Health check and readiness endpoints for web services — liveness probes,
97
99%
Does it follow best practices?
Impact
94%
3.61xAverage score across 4 eval scenarios
Passed
No known issues
Every deployed service needs health endpoints. Load balancers, container orchestrators, and monitoring tools depend on them.
| Endpoint | Purpose | Checks | Returns |
|---|---|---|---|
GET /health | Liveness — is the process running? | Nothing (always 200 if reachable) | {"status": "ok"} |
GET /ready | Readiness — can it serve requests? | Database connection, dependencies | {"status": "ok"} or {"status": "degraded", "checks": {...}} |
app.get("/health", (_req, res) => {
res.json({ status: "ok", timestamp: new Date().toISOString() });
});
app.get("/ready", (_req, res) => {
const checks: Record<string, string> = {};
// Check database
try {
db.prepare("SELECT 1").get();
checks.database = "ok";
} catch {
checks.database = "failed";
}
const allOk = Object.values(checks).every(v => v === "ok");
res.status(allOk ? 200 : 503).json({
status: allOk ? "ok" : "degraded",
checks,
timestamp: new Date().toISOString(),
});
});@app.get("/health")
async def health():
return {"status": "ok", "timestamp": datetime.utcnow().isoformat()}
@app.get("/ready")
async def ready():
checks = {}
try:
db.execute("SELECT 1")
checks["database"] = "ok"
except Exception:
checks["database"] = "failed"
all_ok = all(v == "ok" for v in checks.values())
status_code = 200 if all_ok else 503
return JSONResponse(
status_code=status_code,
content={"status": "ok" if all_ok else "degraded", "checks": checks}
)mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
writeJSON(w, 200, map[string]string{"status": "ok"})
})
mux.HandleFunc("GET /ready", func(w http.ResponseWriter, r *http.Request) {
checks := map[string]string{}
if err := db.Ping(); err != nil {
checks["database"] = "failed"
} else {
checks["database"] = "ok"
}
allOk := true
for _, v := range checks { if v != "ok" { allOk = false } }
status := 200
if !allOk { status = 503 }
writeJSON(w, status, map[string]any{"status": map[bool]string{true: "ok", false: "degraded"}[allOk], "checks": checks})
})Readiness must return 503 until the service is fully initialized — database connected, caches warmed, migrations applied. Register the readiness route early but gate it:
let isReady = false;
app.get("/ready", (_req, res) => {
if (!isReady) return res.status(503).json({ status: "starting" });
// ... normal dependency checks
});
// After all initialization completes:
await db.connect();
await runMigrations();
isReady = true;Without this, the load balancer sends traffic before the service can handle it.
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
failureThreshold: 3 # restart after 3 consecutive failures
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 3 # remove from service after 3 failures
startupProbe: # for slow-starting services
httpGet:
path: /health
port: 8080
failureThreshold: 30
periodSeconds: 2 # up to 60s to startHEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1GET /health returns 200 with {"status": "ok"}GET /ready checks database and returns 503 if down