Most /health endpoints are written in five minutes and then ignored until an uptime tool or a Kubernetes probe starts lying about production. The endpoint is simple. The design choices are not.
Two different questions
Liveness: Is this process alive? If no, restart it.
Readiness: Should this instance receive traffic right now? If no, leave it running but take it out of the pool.
If you jam both into one URL and then point your public uptime monitor at it, a brief database blip marks the whole product "down" for customers on your status page. Sometimes that is the truth. Often it is a deploy, a connection pool stall, or a dependency timeout that self-heals in twenty seconds.
A workable split:
GET /healthor/healthz— process up, no auth, no secrets, responds in well under a second. Safe for external monitors.GET /ready— optional deeper checks (DB ping with a hard timeout). Used by the orchestrator, not always by the public status page.
Rules that keep it honest
Return 200 when healthy, 503 when not. Do not invent a 200 with {"status":"down"} and expect every tool to parse JSON the same way.
Do not require authentication on the public liveness URL. Probes are not your users. If you need a detailed diagnostic payload, put it on an internal route.
Time-box dependency checks. A health check that waits ten seconds on a locked database will cascade failures under load — exactly when you need the probe to stay cheap.
Never ship stack traces, connection strings, or hostnames of internal services in the public body. {"ok":true} is enough for the outside world.
Monitor the same URL customers' networks can reach — production HTTPS, not localhost, not an internal service name.
The lie modes
- Always 200 → you will sleep through real outages.
- 200 only if Stripe, DNS, and three caches all answer → you will page for other people's brownouts.
- Slow under load → load balancers remove capacity when you need it most.
The boring middle: public /health means "this app process can answer HTTP." Separate deep checks for the things you are willing to wake a human for. Point uptime monitoring at the public one, and make sure a 200 still means the product is usable — which is why a thin health check is not a substitute for also watching login or a critical API path.
A sentence to leave in the PR
This endpoint exists so machines can ask if we are up. It should be faster and dumber than a real user request, and it should not pretend the whole universe is healthy.