The Developer's Guide to Writing Better Health Check Endpoints
A health check that just returns 200 OK is almost useless. Here's how to build health endpoints that give your monitoring real, actionable information.
The Developer's Guide to Writing Better Health Check Endpoints
Every monitoring system hits a health check endpoint. But what that endpoint returns determines whether your monitoring is actually useful or just giving you false confidence.
The Levels of Health Checks
Level 0: The Useless Health Check
GET /health → 200 OK
This tells you the web server process is running. That's it. The database could be down, the cache could be empty, and critical services could be unreachable.
Level 1: The Basic Health Check
GET /health → 200 OK
{ "status": "ok", "timestamp": "2026-03-27T10:00:00Z" }
Slightly better — the timestamp proves the response is fresh, not cached.
Level 2: The Dependency Health Check
GET /health → 200 OK
{
"status": "ok",
"dependencies": {
"database": { "status": "ok", "latency_ms": 3 },
"redis": { "status": "ok", "latency_ms": 1 },
"stripe": { "status": "ok", "latency_ms": 145 }
}
}
Now we know if dependencies are healthy and how fast they're responding.
Level 3: The Comprehensive Health Check
GET /health → 200 OK
{
"status": "ok",
"version": "2.4.1",
"uptime_seconds": 86421,
"dependencies": {
"database": { "status": "ok", "latency_ms": 3, "connections": "45/100" },
"redis": { "status": "ok", "latency_ms": 1, "memory": "234MB/512MB" },
"queue": { "status": "ok", "depth": 12, "consumers": 4 }
},
"checks": {
"disk_space": { "status": "ok", "used": "67%" },
"memory": { "status": "ok", "used": "72%" }
}
}
Best Practices
Return Appropriate Status Codes
- 200 — Everything is healthy
- 503 — Service is unhealthy (this tells load balancers to stop sending traffic)
Separate Liveness from Readiness
- /health/live — Is the process running? (For orchestrator liveness probes)
- /health/ready — Can it serve traffic? (For load balancer readiness)
Don't Make Health Checks Too Expensive
A health check that runs 5 database queries every 30 seconds can itself become a performance problem. Keep checks lightweight.
Include Version Information
Knowing which version is running helps correlate issues with deployments.
Protect Sensitive Details
Don't expose connection strings, credentials, or internal IPs. Health checks should provide status, not configuration.
Consider a Degraded State
Not everything is binary. If Redis is down but the app can function (slowly), report degraded rather than unhealthy:
{ "status": "degraded", "reason": "Redis unavailable, using database fallback" }
A good health check endpoint is the foundation of effective monitoring. Invest 30 minutes in building a proper one and every monitoring tool you use becomes more effective.
Written by
UptimeGuard Team
Related articles
Uptime Monitoring vs Observability: Do You Need Both?
Monitoring tells you something is broken. Observability tells you why. Understanding the difference helps you invest in the right tools at the right time.
Read moreCron Job Monitoring: How to Know When Your Scheduled Tasks Fail
Cron jobs fail silently. Backups don't run, reports don't send, data doesn't sync — and nobody notices for days. Here's how heartbeat monitoring fixes that.
Read moreMonitoring Stripe, PayPal, and Payment Gateways: Protect Your Revenue
Every minute your payment processing is down, you're losing real money. Here's exactly how to monitor payment gateways to catch failures before your revenue does.
Read more