By the Octri team · Updated
How to set up an API health check: liveness, readiness and uptime checks
It's tempting to call an endpoint /health, return 200, and consider monitoring finished. That endpoint may tell you the process is still running. It may tell you almost nothing about whether the API can serve an actual customer.
A useful health check starts with the decision its result will trigger. Restarting an unhealthy process, taking an instance out of rotation, and warning an on-call engineer are three different actions. They need different signals.
How do you set up an API health check?
Give each check one job. Use /livez to show that the process is alive. Use /readyz to say whether this instance can handle traffic. Use an external uptime monitor to verify that a real caller can reach the public API.
| Check | Meaning | Typical action |
|---|---|---|
| Liveness | The process can respond | Restart a stuck process |
| Readiness | Required dependencies are usable | Stop routing new traffic to this instance |
| Uptime | The public service responds correctly and quickly | Open an incident or page someone |
The distinction matters on Kubernetes. A failed liveness probe can restart the container; a failed readiness probe removes it from service endpoints. Kubernetes warns against making liveness depend on an external system, because restarting every healthy application instance won't repair a database outage.
Four endpoints, four failures
For this guide I ran a small local HTTP server with a deliberately slow dependency. The same process answered three paths. /livez returned immediately; /readyz treated the dependency as unavailable once its 50 ms allowance was exceeded; /naive waited the full simulated 180 ms and then returned success.
The results were revealingly ordinary:
| Probe | Database slow | Elapsed in the local test |
|---|---|---|
/livez |
200 |
~1 ms |
/readyz |
503 |
~51 ms |
/naive |
200 |
~181 ms |
The naive endpoint's answer was technically sincere: its database call eventually finished. Operationally, the answer was unhelpful. The API was already taking longer than its intended dependency budget, and the check did not report that fact.
This is a controlled example, not a measurement of a production database. A real readiness probe must enforce the deadline on the actual dependency call, not merely sleep and decide afterward.

What a readiness check should do
First decide which dependencies make the instance unable to serve its primary function. A write-heavy orders API may need its database, but might still accept orders while a noncritical email provider is down. Return an overall failure only for dependencies that prevent the important requests from succeeding.
Set a short timeout per dependency and run independent checks concurrently. Do not allow one stuck network call to stall the health endpoint itself. A response can be both compact and diagnostic:
HTTP/1.1 503 Service Unavailable
Content-Type: application/health+json
Cache-Control: no-store
{
"status": "fail",
"checks": {
"database:responseTime": [
{"status": "fail", "observedValue": 500, "observedUnit": "ms"}
],
"cache:responseTime": [{"status": "pass"}]
}
}A health check shouldn't run an expensive full-table query or perform a write on every probe. Do the smallest operation that exercises the dependency you need. If a dependency is optional, mark it as a warning in the body and keep the overall status consistent with your platform's routing decision.
The health check response format
A bare 200 or 503 is enough for most load balancers. For humans and richer monitors, the Health Check Response Format for HTTP APIs describes a JSON structure with an overall status and named checks. It is an Internet-Draft, not a universally adopted standard; decide whether the format suits your consumers.
Avoid returning internal connection strings, database hostnames, secrets, or full exception messages to an unauthenticated public endpoint. A public probe can receive a simple status, while an internal probe or observability system retains the detailed reason.
Cache-Control: no-store is also sensible. You don't want an intermediary serving a healthy answer that predates the failure.
Monitor it from outside
Internal readiness probes can all be green while users cannot resolve your domain, complete TLS, or reach the load balancer. An external uptime check fills that gap. It should call a representative public route, from more than one location if your audience is geographically distributed.
Choose the interval, timeout, acceptable status, and latency budget intentionally. A response can be 200 and still arrive too late to be useful. For a latency-sensitive endpoint, counting a 300 ms success as healthy against a 100 ms budget gives your callers a more honest signal than status alone.

Test the monitor's failure path as well as its success path: break a non-production dependency, verify the check fails, see whether the alert reaches the right person, and confirm that a recovery message appears when the system returns to normal.

If the API offers hundreds of routes, start with a few that represent important workflows. One health endpoint cannot prove that every authenticated or write operation works, so pair the uptime checks with real-traffic error and latency monitoring.
Checklist for API health checks
- Keep liveness shallow and free of remote dependencies.
- Make readiness reflect only dependencies required to serve traffic.
- Enforce explicit deadlines on dependency calls.
- Use
503when the instance should stop receiving new requests. - Return a small, safe response body that names failing checks.
- Set
Cache-Control: no-storefor frequently changing health responses. - Monitor a representative public URL from outside your infrastructure.
- Treat unexpectedly slow
200responses as failures if they break the service budget. - Verify alert delivery and recovery before the system goes live.
API health checks with Octri
Octri Monitoring can probe public API endpoints on a schedule, track their latency and uptime, and show the result on a public status page. You can configure acceptable status codes, timeouts, and a latency budget for each check. It can also generate checks for suitable GET operations from an OpenAPI specification.
An external check complements the application's /livez and /readyz endpoints rather than replacing them. If you keep those roles separate, the check that restarts a process won't trigger a restart storm just because a shared dependency is struggling.