All posts

Healthcheck Test: A Practical Guide for Developers

August 23, 2026

What Is a Healthcheck Test?

A healthcheck test is any check — manual or automated — that hits a health endpoint and verifies the response actually proves the service is working, not just that something answered the socket. A generic uptime ping only confirms a port is open or a server returns some response. A real healthcheck test inspects what came back: the status code, the body, and whether the service's real dependencies are functioning.

The health check endpoint itself is usually a lightweight route like /health or /healthz that a load balancer, orchestrator, or monitoring tool can call cheaply and frequently. What separates a good healthcheck test from a rubber-stamp ping is the verification logic wrapped around that call. Anyone can confirm a 200 came back; a good test confirms the 200 means what you think it means.

What a Good Healthcheck Test Should Verify

A solid healthcheck test checks more than "did it respond." At minimum, it should verify:

  • Status code correctness — a 200 for healthy, and a distinct non-200 (503 is standard) when something's degraded. Relying on 200 for everything hides failures from anything parsing the response.
  • Response body and schema — not just presence, but shape. A healthcheck test example worth trusting parses the JSON and confirms expected fields exist, rather than just checking the connection succeeded.
  • Dependency checks — does the service confirm its database, cache, or queue connections are actually reachable, or does it just report "I'm running"?
  • Response time thresholds — a health endpoint that takes 8 seconds to respond is itself a symptom, even if it eventually returns 200.

For the healthcheck response format, it's worth adopting a structure instead of inventing your own ad-hoc JSON every time. The IETF health+json draft proposes a standardized shape with a top-level status field (pass, fail, or warn) and per-component detail, giving both humans and monitoring tools a predictable contract. Guidance on keeping these endpoints cheap and side-effect-free is also covered well in this piece on REST API health check endpoint best practices, which stresses meaningful status codes and avoiding stale, cached responses.

Liveness Tests vs Readiness Tests

Not every healthcheck test should ask the same question. A liveness test asks "is the process still running and not deadlocked?" — a shallow check that should return fast and touch nothing external. A readiness test asks "can this instance actually serve traffic right now?" — a deeper check that's allowed to verify database connections, downstream APIs, or queue depth.

Confusing the two is a common source of outages. If your liveness endpoint pings the database and that database has a momentary hiccup, an orchestrator might kill and restart a perfectly healthy process — a classic shallow-vs-deep health check mismatch. Liveness should stay shallow; readiness is where the deep checks belong. Better Stack's guide to server health checks covers this distinction further, including why caching either endpoint's response defeats the purpose.

How to Manually Test a Healthcheck Endpoint

Before wiring anything into CI or a monitoring dashboard, test the endpoint by hand. A few curl commands cover most of what you need:

Check the status code:

curl -s -o /dev/null -w "%{http_code}\n" https://api.example.com/health

Check the JSON body:

curl -s https://api.example.com/health | jq .

Check response time:

curl -s -o /dev/null -w "time_total: %{time_total}s\n" https://api.example.com/health

Run these against a healthy instance first so you know what "good" looks like, then intentionally break a dependency (stop the local database, for example) and run them again. If the status code and body don't change, your healthcheck test isn't testing anything — it's just confirming the server process exists.

How to Automate Healthcheck Testing

Manual curl checks build confidence during development; automated health check testing is what keeps you honest in production. Two layers are worth setting up:

  1. In CI or integration tests, add an assertion that hits /health after deployment and fails the pipeline if the status code or schema doesn't match expectations. This catches broken health endpoints before they ship, not after.
  2. External synthetic monitoring, where a scheduled service polls your health endpoint from outside your network on a fixed interval and alerts when it fails. Synthetic monitoring is valuable precisely because it sees what your users see — network issues, DNS problems, and regional outages that internal checks miss.

Whichever you use, alert on a failure threshold — say, three consecutive failures — rather than a single blip. A one-off timeout during a deploy isn't the same signal as a service that's been down for five minutes straight.

Common Mistakes That Make Healthcheck Tests Unreliable

Most healthcheck test mistakes fall into a short list of repeat offenders:

  • Caching the response. A cached "healthy" response from two minutes ago tells you nothing about right now.
  • Liveness checks that touch dependencies. This causes unnecessary restarts when a downstream service, not your app, is having a bad moment.
  • No timeout on the check itself. A hanging health check can block deploys or false-negative an otherwise fine service.
  • Alerting on a single failure. This produces noisy false positive health checks that train teams to ignore alerts.
  • No separation between public and detailed responses. A public health endpoint shouldn't leak internal infrastructure details; a separate authenticated route can carry deeper diagnostics.

Each of these is easy to introduce accidentally and easy to fix once you know to look for it.

Why a Healthcheck Test Can't Cover Cron Jobs

Everything above assumes a long-running process you can poll right now. A healthcheck test proves a service is alive at the exact moment you send the request — nothing more. That model breaks down completely for scheduled jobs.

A cron job has no listening process between runs. There's nothing to poll at 3 a.m. when the job isn't executing. A healthcheck test vs cron monitoring comparison exposes the gap fast: your API can return a perfect 200 all night while the nightly export job silently failed, timed out, or never ran at all. Passing a healthcheck says nothing about whether the last scheduled run happened or succeeded — it's an orthogonal question. This is also why Docker's own HEALTHCHECK directive, useful as it is for containers, doesn't extend to cron — a gap covered in detail in Docker Healthcheck: Full Syntax Guide + Why It Misses Cron. If you're debugging container-specific health states, Docker Health Check: States, Commands, and Fixing Unhealthy is the relevant deep dive, and for the broader reliability picture across distributed schedulers, see Distributed Job Scheduling: How It Works, Where It Fails.

To monitor scheduled jobs properly, you need something that tracks execution history — did the job start, finish, and finish on time — not just whether an endpoint responds.

A healthcheck test tells you a service is up right now. It can't tell you whether last night's cron job ran, ran on time, or failed silently while nobody was watching.

That's the gap Cronevra is built for: it watches your scheduled job's actual execution history and sends recovery alerts when a run doesn't happen the way it should. If your stack leans on cron jobs the way most backends do, it's worth seeing what that visibility looks like — check the Cronevra pricing page to get started.

Frequently Asked Questions

Is a healthcheck test the same as an uptime check?

No. An uptime check typically just confirms a server responds to a request, while a healthcheck test verifies the response content — status code, body, and dependency status — to confirm the service is functionally working, not merely reachable.

What HTTP status code should a healthcheck test expect?

A healthy service should return 200, and a degraded or failing one should return a distinct non-200 code, commonly 503. Using 200 for every state, including failures, defeats the purpose of the check.

Should a healthcheck test check the database connection?

Only in a readiness check, not a liveness check. Liveness checks should stay shallow and dependency-free so a database hiccup doesn't trigger an unnecessary process restart, while readiness checks can safely go deeper.

How often should a healthcheck test run?

Most production setups poll every 10–30 seconds for internal liveness/readiness checks and every 1–5 minutes for external synthetic monitoring. The right interval depends on how fast you need to detect and react to failure.

Can I use a healthcheck test to monitor a cron job?

No. A healthcheck test only proves a long-running process is alive at the moment you poll it, and cron jobs have no listening process between runs to poll. Monitoring scheduled jobs requires tracking execution history — start time, completion, and success — which tools like Cronevra are built for.

What's the best way to test a health check endpoint locally before deploying?

Run curl commands against the endpoint while it's healthy to establish a baseline, then deliberately break a dependency (stop the database, disconnect the cache) and run the same commands again. If the response doesn't change when a dependency is down, the check isn't verifying anything real.