How to Implement Health Check Endpoints Properly

Health check endpoints seem simple but are frequently implemented poorly — either too shallow to be genuinely useful, or too deep in ways that cause cascading failures. This guide covers implementing them correctly.

The Common Health Check Mistakes

Two frequent problems: a health check that always returns "healthy" regardless of genuine application state (providing false confidence), or a health check that transitively checks every dependency so deeply that a single downstream issue cascades into marking many services unhealthy simultaneously.

Distinguishing Liveness from Readiness

See How to Set Up Liveness and Readiness Probes in Kubernetes for the Kubernetes-specific application of this same general principle — even outside Kubernetes, distinguish "is the process alive" (liveness) from "is it ready to serve traffic" (readiness); conflating these causes the problems above.

A Genuinely Useful Liveness Check

app.get('/healthz', (req, res) => {
  res.status(200).send('OK');
});

Should be extremely simple/fast — confirms the process itself is responsive, without checking external dependencies that shouldn't cause this specific process to be considered "dead."

A Genuinely Useful Readiness Check

app.get('/ready', async (req, res) => {
  const checks = await Promise.allSettled([
    checkDatabase(),
    checkCache(),
  ]);
  const allHealthy = checks.every(c => c.status === 'fulfilled');
  res.status(allHealthy ? 200 : 503).json({ checks });
});

Checks genuine operational readiness (can this instance actually serve requests correctly) — appropriate for load balancer health checks removing genuinely unready instances from rotation.

Avoiding Deep Transitive Dependency Checks

A readiness check shouldn't transitively check every service your dependencies depend on — check your own direct, immediate dependencies only; deep transitive checking causes exactly the cascading-failure pattern this article warns against.

Setting Appropriate Timeouts on Dependency Checks

async function checkDatabase() {
  return Promise.race([
    db.query('SELECT 1'),
    new Promise((_, reject) => setTimeout(() => reject('timeout'), 2000)),
  ]);
}

A hanging dependency check shouldn't hang the entire health check response — set explicit timeouts, treating a timeout as an unhealthy signal rather than leaving the health check itself unresponsive.

Returning Useful Detail in the Response

{
  "status": "unhealthy",
  "checks": {
    "database": "ok",
    "cache": "timeout"
  }
}

Include which specific check failed, not just an overall pass/fail — genuinely useful for quick diagnosis without needing to dig through logs separately when a health check reports unhealthy.

Excluding Health Check Endpoints from Regular Logging Noise

If health checks run frequently (every few seconds from a load balancer), consider excluding them from regular access logs (or logging at a lower verbosity) to avoid drowning genuine request logs in health check noise.

Testing Health Check Behavior Under Failure Conditions

Deliberately simulate a dependency failure and verify your health check correctly reflects this — a health check that's never actually tested against real failure conditions may not behave as intended when genuinely needed.

Common Errors

Load balancer removes healthy instances during a brief downstream blip — review whether your readiness check is too strict/sensitive to transient downstream issues; consider a small grace period or retry before marking an instance unready for a single failed check.

Continue Reading

Browse more articles in Advanced Observability & Incident Management.

  • health check endpoint best practices, liveness vs readiness check, health check dependency timeout, avoid cascading health check failure
  • 0 Usuários acharam útil
Esta resposta lhe foi útil?

Artigos Relacionados

What Is Observability? Metrics, Logs, and Traces Explained

Observability goes beyond basic monitoring — it's the ability to understand what's...

How to Set Up Centralized Logging with the ELK Stack (Elasticsearch, Logstash, Kibana)

The ELK Stack (Elasticsearch, Logstash, Kibana) is a mature, powerful centralized logging...

How to Set Up Centralized Logging with Grafana Loki (Lightweight Alternative)

Grafana Loki is a lighter-weight alternative to the ELK Stack, designed to index only log...

How to Implement Distributed Tracing with Jaeger

Distributed tracing tracks a single request as it flows through multiple services —...

How to Instrument an Application with OpenTelemetry

OpenTelemetry is the current industry-standard framework for generating metrics, logs, and traces...