Health check endpoints: why every API needs one
作者:Seaworthy · 2026年4月1日
The ten lines of code your production system is missing
There's a class of bug that never shows up in unit tests, never triggers a linter, and never gets caught in code review. The API starts up, passes health checks, and then quietly stops working somewhere between "the database connection pool is exhausted" and "the disk is 100% full." Nobody finds out until a user tweets about it.
A GET /health endpoint won't prevent those failures. But it will tell you about them in under thirty seconds instead of thirty minutes.
What a health check actually is
A health check endpoint is an HTTP route, typically at /health, /healthz, or /status, that your service exposes so that load balancers, container orchestrators, and monitoring tools can ask a single question: is this instance ready to serve traffic right now?
It is not a metrics endpoint. It is not a status page for end users. It is a machine-readable, low-latency probe that returns a structured response about the internal state of your application.
At minimum, a healthy response looks like this:
{
"status": "ok",
"uptime": 4823,
"timestamp": "2026-06-15T10:42:00Z"
}
And a degraded or unhealthy response:
{
"status": "degraded",
"checks": {
"database": "ok",
"redis": "timeout",
"disk": "ok"
},
"timestamp": "2026-06-15T10:42:00Z"
}
The HTTP status code matters too. Return 200 when healthy, 503 when something is broken. Load balancers and orchestrators read the status code, not the body.
Why load balancers need it
Kubernetes, ECS, Nginx, and every cloud load balancer worth using support health check probes out of the box. If you don't configure one, they fall back to checking whether the TCP port is open. That's a terrible proxy for "the service is actually working."
TCP says your process is alive. A real health check says your process can do its job. The database connection might be severed. The background worker might be deadlocked. The config file might have failed to reload after a deploy. TCP has no idea about any of that.
With a proper /health endpoint, a Kubernetes liveness probe looks like this:
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 5
periodSeconds: 10
failureThreshold: 3
Three failed checks and Kubernetes restarts the container. Without this, a deadlocked process sits there indefinitely, serving errors, and the orchestrator has no idea.
The readiness vs liveness distinction
This trips people up. They're different concepts and conflating them causes real problems.
A liveness probe answers: is the process broken in a way that requires a restart? It should never check dependencies. If your database is down, you don't want Kubernetes to restart every instance of your API in a death spiral.
A readiness probe answers: is this instance ready to receive traffic? It should check dependencies. If the database connection isn't established yet after startup, the instance should be temporarily removed from the load balancer rotation until it is.
So in practice you want two routes:
GET /healthz/live → 200 if the process is not wedged
GET /healthz/ready → 200 if dependencies are reachable
The liveness check is dumb on purpose. It returns 200 unless the process is in a state so bad that only a restart will help. The readiness check is where you ping your database, check your Redis connection, and verify that anything the service can't function without is actually available.
What to check (and what not to)
Checks worth including in a readiness probe:
- Database connectivity (a cheap
SELECT 1query, not a full table scan) - Cache connectivity
- Any queue or message broker the service publishes to
- Disk space if your service writes files
Checks to leave out:
- External third-party APIs your service calls. If Stripe is down, your service isn't broken, Stripe is. Don't let a Stripe outage take your instances out of rotation.
- Slow computations. Health checks should respond in under 100ms. If yours takes longer, something is wrong with the check itself.
The auth question
Don't put your health endpoint behind authentication. The whole point is that external systems can probe it without credentials. The response should not leak sensitive information: no connection strings, no internal IP addresses, no stack traces. Stick to status strings and timing data.
If you're worried about information disclosure, the solution is to keep the response minimal, not to add auth.
Why this keeps getting skipped
I genuinely don't know how to feel about how often I see production APIs with no health endpoint at all. It takes maybe an hour to add one properly, it costs nothing at runtime, and the payoff in operational visibility is immediate. But it's invisible work. It doesn't ship a feature. Nobody demos it at a sprint review.
And so it gets skipped, and then at 2am someone is staring at a load balancer that has no idea three of its five backend instances are returning 500s on every request.
The irony is that this is one of the simplest things to verify statically. If a codebase has no route matching /health or /healthz, that's a findable fact before the code ever hits production.
Seaworthy's ops basics checks flag missing health endpoints as part of its standard repository scan.
本文由 AI 生成,总结了公开可用的来源。
Seaworthy 会扫描你的代码仓库,发现类似本文提到的各种问题。
安全漏洞、泄露的密钥和配置错误——在部署前捕获。免费运行,无需账户。