Health Checks
A health check is how a system says 'I am ready to serve'. Done well, it routes traffic safely; done poorly, it lies.
What you'll learn
- Distinguish liveness from readiness checks
- Decide which dependencies a check should test
- Avoid health checks that cause cascading failures
- Use upstream status signals alongside your own checks
6 min
Liveness versus readiness
Two questions sound alike but differ sharply. Liveness asks "is this process alive, or stuck and in need of a restart?" Readiness asks "can this instance serve traffic right now?" An instance can be alive but not ready — for example, still warming a cache or waiting on a dependency at startup.
Orchestration systems use them differently: a failed liveness check triggers a restart, while a failed readiness check merely removes the instance from rotation until it recovers. Confusing the two leads to needless restarts of healthy processes or routing traffic to instances that cannot yet handle it.
What a check should test
A readiness check should verify the things an instance genuinely needs to serve a request — its own initialisation, and the critical dependencies it cannot work without. Keep it lightweight: a health check that runs an expensive query on every poll adds load precisely when the system is fragile.
GET /healthz
200 OK { 'status': 'ready' }
503 { 'status': 'degraded', 'db': 'unreachable' }Return a clear status code so callers and load balancers can act on it without parsing prose. Reserve the body for human-readable detail.
Avoid the cascade trap
Health checks can cause the very outage they are meant to prevent. If every instance marks itself unhealthy the moment a shared dependency wobbles, the load balancer removes them all and you have a total outage from a partial one. This is a common and painful failure mode.
Be deliberate about which dependencies should fail a check. A non-critical dependency being down might warrant a degraded response, not a hard failure, so the instance keeps serving what it still can. This connects to graceful degradation: staying partly useful beats going fully dark.
Lean on upstream signals
Your checks describe your own health, but you also depend on services you do not run. Watch the published health of those upstreams so you can tell an external outage apart from a fault of your own. Knowing the dependency is down stops you restarting healthy instances in a fruitless attempt to fix something outside your control.
For platform health, our status page reports current state and incidents. Treat it as an input to your operational picture, alongside your own readiness and liveness checks, rather than a replacement for them.
Key takeaways
- Separate liveness (restart me) from readiness (route to me)
- Test only what an instance truly needs, and keep checks cheap
- Avoid checks that fail every instance at once and cause a cascade
- Use a degraded state when a non-critical dependency is down
- Cross-reference upstream status to isolate external outages
FAQ
Should a readiness check test every dependency?
No. Test only the dependencies the instance cannot serve without. Including a non-critical dependency means an outage there needlessly removes otherwise-healthy instances from rotation.
How often should health checks run?
Frequently enough to react quickly, but not so often that the checks themselves add meaningful load. A few seconds between polls is typical; keep each check inexpensive so frequency stays cheap.
Can a health check make an outage worse?
Yes. A check that fails all instances when a shared dependency hiccups turns a partial problem into a complete one. Decide carefully which conditions should fail the check versus return a degraded status.
Ready to build?
Read the API reference, grab the OpenAPI spec, and ship a resilient integration.