Health Checks & Failover
A load balancer is only useful if it knows which servers are alive. Health checks are the mechanism — the LB periodically probes each backend to determine if it can handle traffic. When a server fails, failover kicks in to route around the damage automatically.
Why Health Checks Matter
Without health checks, a load balancer blindly sends requests to dead servers. Users get connection timeouts, 502 errors, and a terrible experience. Health checks detect problems before users hit them.
Active vs Passive Health Checks
Active Health Checks
The LB periodically sends probe requests (every 5-30 seconds) to each backend:
- TCP check: Can I open a connection to port 8080?
- HTTP check: Does
GET /healthreturn status 200? - Custom check: Does the response include
{"db": "ok", "cache": "ok"}?
If a server fails N consecutive checks (e.g., 3), it's marked unhealthy and removed from the pool.
Passive Health Checks
The LB observes real traffic responses — no extra probe requests needed:
- Count 5xx errors: if a server returns 5 errors in 30 seconds → mark unhealthy
- Track timeouts: if responses take >10s consistently → mark degraded
- Monitor connection resets: TCP RST from backend → something is wrong
Passive checks are faster to detect issues (no waiting for next probe interval) but only work if the server is receiving traffic.
Liveness vs Readiness Probes
Kubernetes Concepts That Apply Broadly
- Liveness probe: "Is this process alive?" If it fails, restart the container. Detects deadlocks, infinite loops, corrupted state.
- Readiness probe: "Can this instance handle traffic right now?" If it fails, remove from the LB pool but don't restart. Detects startup in progress, dependency unavailable, overloaded.
The distinction matters: a server starting up is alive (don't restart it) but not ready (don't send it traffic yet).
What to Check
Health Endpoint Design
A good /health endpoint checks what matters:
- Shallow check:
GET /health→ 200 OK (process is up and listening) - Deep check:
GET /health/ready→ verifies DB connectivity, cache reachable, disk space OK
Caution: Deep checks can backfire. If your DB has a momentary hiccup, all servers might simultaneously fail their health check, causing a total outage. Consider: should a DB issue mark the server unhealthy, or should the server return degraded responses?
Failover Strategies
DNS-Based vs LB-Based Failover
- LB-based: Instant. LB detects failure in seconds and stops routing. No client changes needed. Best for services behind a load balancer.
- DNS-based: Slow (minutes). Change DNS record to point to standby. Clients cache old DNS (TTL). Used for region-level failover or when LB itself fails.
Lesson: Use LB-based failover for individual servers. Reserve DNS failover for catastrophic (entire datacenter) failures.
Cascading Failures: When Health Checks Go Wrong
The Thundering Herd Problem
Scenario: 5 servers behind a LB. Server A goes down. The LB redistributes A's traffic across B, C, D, E. But now each handles 25% more load. If they were at 80% capacity, they're now at 100%. Server B buckles, fails its health check. Now C, D, E handle even more... cascade failure.
Mitigations:
- Keep servers at <60% capacity (headroom for failover absorption)
- Use gradual ramp-up (don't send full traffic to a recovering server instantly)
- Circuit breakers: stop retrying a failing dependency
- Shed load intentionally (return 503) rather than collapsing entirely
🏢 Real-World: GitHub's Graceful Deploy Failover
When GitHub deploys new code, they use health checks as a coordination mechanism:
- Drain: Server signals "not ready" → LB stops sending new requests
- Wait: Existing requests finish (graceful shutdown period)
- Deploy: New code is deployed and server starts up
- Warm: Server initializes caches, loads config
- Ready: Health check passes → LB routes traffic back
Users never see errors because the LB routes around servers being deployed. This "rolling deploy" pattern enables GitHub to deploy dozens of times per day with zero downtime.
Interactive: Kill Servers and Watch Failover
Click servers to "kill" them and watch the LB detect failure and reroute traffic: