The load balancer marks the backend "healthy": port 443 responds. Meanwhile PostgreSQL refuses connections — every API request returns 503. Users see an outage; host monitoring shows green. The team loses twenty minutes hunting "network" when the health check never tested the database.
This scenario repeats weekly on stacks that look well configured: valid TLS certificate, active web process, TCP probe passing. The problem is not the host — it is the question asked by the probe.
An application health check answers a precise question: can this instance handle a representative request right now? Not: is someone listening on a port? The distinction changes everything when a critical dependency fails without stopping the web server.
TCP, shallow HTTP and deep check
Teams often hesitate between three probe levels. Each has its place — but only the third protects the end user.
| Type | Checks | Limit |
|---|---|---|
| TCP connect | Process listening | Fast, blind |
HTTP 200 / | Home page | May be CDN-cached |
HTTP /health/live | Process + runtime | Minimal |
HTTP /health/ready | DB, cache, deps | Remove traffic if fail |
/health/live serves liveness (Kubernetes): no database — avoids crashloop when Postgres is down but the application still runs. /health/ready serves readiness: critical dependencies only — database, cache, queue if essential.
A check that passes when DB is dead sends traffic into a void. This is the most frustrating case: all green on infra, all red for the client.
Design fast reliable checks
A good health endpoint follows four rules. First, a timeout under 1–2 seconds, aligned between load balancer and kubelet — a slow check delays traffic removal when you need it most.
Second, no side effects: no test order creation in production, no email send, no database write. The probe reads state; it does not modify it.
Third, a stable JSON response: { "status": "ok", "checks": { "db": "ok" } } — parseable by monitoring and readable by a human during an incident.
Finally, a 2–5 second cache in memory if probes are aggressive. Avoid reusing a heavy page (WordPress admin, full dashboard) as probe: you waste CPU on every poll.
Host LB, Kubernetes and shared hosting
OVHcloud, Hetzner and Scaleway offer load balancers with configurable HTTP probes. Set the check on /health/ready, a reasonable interval (5–10 seconds) and consecutive up/down thresholds to avoid flapping.
On Kubernetes, align probe paths with the external load balancer — inconsistent double checking causes permanent healthy/unhealthy toggling.
On shared hosting without custom probes, monitor an external URL (UptimeRobot, etc.) with alerts. This is a complement, not a substitute for an internal probe testing dependencies.
Security and information leak
A public /health listing versions, environment and cluster name is an invitation. Rate-limit, internal network, IP allowlist. In sensitive infrastructure, mTLS between load balancer and application.
An attacker probing /health sometimes discovers database state, framework version or internal topology — useful information for a targeted attack.
The climax: green on monitoring, red for the customer
The probe must match minimal user journey — not transport layer.
Decide and move forward without blind spots
Implement separate live and ready endpoints, each with a clear responsibility: the first decides whether the process should restart, the second whether the instance can receive traffic. Include only critical dependencies in ready — not the full business stack. Secure the endpoint on internal network or IP allowlist. Align load balancer and Kubernetes on same paths and thresholds. Validate in staging: cut DB → traffic removed without mass pod restart. Compare load balancer offers via the directory and compare tool.
Frequently asked questions
Why is TCP on port 80 rarely enough?
A TCP check only verifies a socket is open — not that the application responds correctly, nor that the database is reachable. The port can be open while PHP-FPM is saturated, the app returns 500 errors or PostgreSQL refuses connections. During an incident, you waste time diagnosing "network" when the problem is application-level.
What should /health/ready contain?
Light checks of critical dependencies: database ping, Redis PING, queue if essential — without running full business logic. Timeout should stay short (1–2 seconds). On failure, the instance is removed from traffic; it is not necessarily restarted, which avoids worsening a database incident.
Public or internal health check?
Prefer an internal endpoint — private network, IP allowlist or mutual authentication. Public unauthenticated /health can become an attacker probe or information leak (software version, database state). If a public endpoint is unavoidable, limit exposed information and apply strict rate-limiting.
How to avoid overloading the app?
Cache the result a few seconds in memory, limit parallel checks and use a dedicated lightweight endpoint without verbose logs. On shared hosting, a load balancer probing every second multiplies load — configure a 5–10 second interval and a light endpoint.
Healthy means "ready to serve" — not "telnet gets a response on the port".