Skip to content
Guides

Health check endpoints: what to return, and how to test one

A health endpoint is a URL that answers one question for a machine: should traffic go here? Getting it wrong either hides outages or causes them. Here is what it should check, what it should return, and what it should never say.

What a health endpoint is for

Three kinds of caller read a health endpoint, and each acts on the answer. A load balancer stops sending traffic to an instance that fails. An orchestrator such as Kubernetes restarts a container or holds it out of service. An uptime monitor alerts a person. All three read the status code first, and most read nothing else.

The name /healthz comes from Google's internal convention of “z-pages”, status pages ending in z so they never collide with a real route. Kubernetes made it common. /health, /status and /ping work just as well: the path matters less than being consistent across your services.

Shallow and deep checks

A shallow check answers “is this process running and able to serve a request?” It touches nothing outside the process and returns in a millisecond or two.

A deep check also asks the things the service depends on: can it reach its database, its cache, its queue. It finds more real problems, and it carries a risk. If every instance reports unhealthy because the shared database had a slow second, the load balancer pulls all of them at once and a blip becomes an outage.

Two rules keep a deep check safe.

  • Run the dependency checks in the background on a timer and have the endpoint report the last result, so the endpoint stays fast and a burst of health checks cannot overload the database.
  • Only fail the check for a dependency the instance cannot work without, and that a different instance could reach. If no instance could reach it, taking this one out of rotation helps nobody.

Liveness and readiness

Kubernetes separates the two questions, and the split is useful anywhere.

  • Liveness: is the process stuck beyond recovery? A failure means “restart me”. Keep it shallow. A liveness probe that checks the database restarts every pod when the database is down, which fixes nothing and adds a cold start to the outage.
  • Readiness: should this instance get traffic right now? A failure means “skip me for a while”. This is where dependency checks belong, along with “still warming up” and “draining for shutdown”.

A common layout is /livez for liveness and /readyz for readiness, with /healthz kept as the readiness answer for anything outside the cluster.

What to return

  • Status code carries the verdict. 200 when healthy, 503 when not. Avoid returning 200 with "status": "down" in the body: most callers never read the body.
  • A small body for people. An overall status and one word per named check is enough to tell an on-call engineer where to look.
  • Cache-Control: no-store. A CDN or proxy that caches a 200 will keep answering healthy after the origin is gone.
  • No authentication on the shallow check. Load balancers rarely send credentials. If you want more detail, put it on a second endpoint behind auth.
  • Fast. Well under the timeout of whatever calls it. A few hundred milliseconds is already slow for a health endpoint.
{
  "status": "ok",
  "checks": {
    "database": "ok",
    "queue": "ok"
  }
}

What to leave out

A public health endpoint is a page anyone can read, so it should say nothing an attacker could use.

  • Stack traces and error messages from a failed dependency check.
  • Connection strings, hostnames of internal services, and IP addresses.
  • Exact versions of your framework, runtime or libraries.
  • Environment variables or configuration dumps, which some frameworks expose by default.

Spring Boot Actuator, for example, can expose environment and configuration endpoints next to /actuator/health. Check which ones are enabled before the health path goes public.

How to test one

Call it the way a load balancer would: from outside your network, with no cookies or credentials, and with a short timeout. Then check the four things that matter: a 2xx when healthy, a fast answer, a small body with nothing sensitive in it, and headers that stop it being cached. Stop a dependency in staging and confirm the readiness check turns 503 while the liveness check stays 200.

The free health check endpoint tester runs that first set of checks against any public URL and grades each one. After that, have something call it every few minutes from more than one region: RealUptime Monitor does that on the free plan and alerts you when it fails.

Next

Uptime monitoring explained: what a green badge actually measures