All posts

Health Checking Explained — And Its Blind Spot for Cron

August 13, 2026

What Is Health Checking, Exactly?

Health checking is the practice of programmatically confirming that a service or process is actually doing what it's supposed to do, rather than assuming it is. Ask most developers what a health check is and you'll get roughly the same answer: an automated ping that returns a status, so a load balancer, orchestrator, or on-call engineer knows whether to trust a system before routing traffic to it or escalating an alert.

That definition holds up in one context and falls apart in another. There are two distinct situations where "is this healthy?" matters:

  • Always-on services — web servers, APIs, background workers that stay running continuously. Something is listening, so something can be polled.
  • Scheduled or one-off jobs — cron tasks, nightly batch scripts, periodic data syncs. These processes start, run briefly, and exit. There's no persistent listener to knock on.

Most content about health checking only covers the first case — because the tooling (Kubernetes probes, load balancer health checks, uptime monitors) was built for services that never stop running. The second case needs a completely different mechanism, which we'll get to after establishing how the standard pattern works.

How a Standard Health Check Endpoint Works

The conventional approach is a lightweight health check endpoint — commonly /health or /healthz — that a service exposes for monitors and orchestrators to poll on a fixed interval, say every 10 or 30 seconds.

The response is deliberately simple. A healthy service returns a 2xx status code, usually with a minimal JSON body confirming key dependencies (database connection, cache, disk space) are reachable. An unhealthy service returns 4xx or 5xx, signaling that something downstream is broken or the process can't serve traffic. These are the health check status codes that load balancers, dashboards, and alerting rules key off.

Kubernetes and similar orchestrators split this into two related but distinct checks:

  • Liveness — is the process itself still running, or has it deadlocked and needs to be restarted?
  • Readiness — is the process ready to accept traffic right now, even if it's technically alive (e.g., still warming up a cache)?

The liveness vs readiness distinction matters because the response to each failure differs: a failed liveness check triggers a restart, while a failed readiness check just pulls the instance out of the load balancer pool temporarily. Both assume the same thing: a process that's continuously running and can be interrupted with a request at any moment.

The Blind Spot: Health Checks Assume Something Is Running

That assumption is exactly where health checking stops working for scheduled tasks. A /health endpoint can return 200 every single time it's polled, all day, all night — and tell you nothing about whether your 2 a.m. billing job, backup script, or data sync actually ran.

This is the structural gap: health checks poll a target that's expected to always be there. A cron job isn't there most of the time by design. It wakes up, executes, and disappears. If it never wakes up — a bad deploy disables the schedule, a container restarts and drops the crontab entry, a dependency silently changes — there's nothing for a health check to hit, and nothing tells you it didn't happen.

This produces the specific failure mode DevOps teams dread: silent job failures. The service hosting the job might be perfectly healthy by every conventional measure while the actual work it's supposed to perform quietly stops happening. Traditional scheduled task monitoring based on service-level health checks can't see this, because the question it answers ("is this process reachable?") isn't the question that matters ("did this specific job run and succeed on schedule?").

The reactive fallback is combing through logs after someone notices a report is missing or a customer complains. That's a real option — see our breakdown of reading a crontab log or how cron logs are structured — but it only works if someone thinks to look, and by then the job has already failed silently for a while.

The Fix: Inverting the Health Check with a Check-In Pattern

The pattern that actually covers scheduled work flips the polling direction entirely. Instead of a monitor pinging a service to ask "are you alive?", the job pings the monitor to say "I just ran." This is the dead man's switch model, more commonly called heartbeat monitoring or check-in monitoring.

The mechanics are straightforward: your cron job, at the end of a successful run, sends an HTTP request to a monitoring endpoint. The monitor expects that check-in on a schedule matching the job's own — every night at 2 a.m., every hour, every fifteen minutes. If the check-in doesn't arrive within an agreed grace period past the expected time, the job is marked overdue and an alert fires.

This inverts the core assumption of traditional health checking. There's no need for a persistent process to poll, because the job itself proves it's alive, on its own terms, only when it actually completes work. A job that never starts, crashes halfway through, or hangs indefinitely simply never sends its heartbeat — and that absence is the signal.

Building a Complete Health-Checking Setup for Scheduled Jobs

A resilient setup treats these as complementary layers, not competing ones. Good health check best practices mean applying each pattern where it fits:

  • Use standard /health endpoints for always-on services — APIs, web servers, anything a load balancer routes to.
  • Use check-in/heartbeat monitoring for anything that runs on a schedule and exits — cron jobs, scheduled HTTP calls, batch scripts.
  • Keep execution history for every job run, not just pass/fail — timestamps, duration, response codes — so you can diagnose why something failed instead of just knowing that it did.
  • Tune recovery alerts and grace periods deliberately. Too tight, and a job that's five minutes late from normal system load triggers a false alarm. Too loose, and a real failure sits unnoticed for hours.

Getting alert thresholds right is mostly about matching the grace period to the job's actual variance in run time, then alerting on both "overdue" and "recovered" states so the team knows when an issue resolves without checking manually.

How Cronevra Handles Health Checking for Scheduled HTTP Jobs

Cronevra builds the check-in pattern directly into cron job monitoring for HTTP-based scheduled work. Instead of polling your job (which isn't possible for something that only runs for seconds at a time), Cronevra expects each job to check in when it starts or finishes an HTTP request, and flags anything that goes quiet past its grace period.

Every run is logged with full execution history — status, timing, and response details — so when something fails, you're not left guessing. Recovery alerts notify your team the moment a monitor goes overdue and again when it comes back, closing the loop without manual log-checking. If you've been relying on a tool that's no longer around, our note on the Cronhub shutdown and alternatives covers what to look for in a replacement.

For HTTP job monitoring where a missed run matters — billing, syncs, backups, alerts — this is the pattern that closes the blind spot standard health checks leave open.

Start a free monitor at Cronevra and see your first check-in within minutes, or compare plans on the pricing page once you know how many jobs you need to cover.

Frequently Asked Questions

What's the difference between a health check and a heartbeat monitor?

A health check polls a running service to ask if it's currently responsive; a heartbeat monitor waits for a job to actively report in after it runs. Health checks fit continuously running processes with something to poll. Heartbeat monitoring fits jobs that start, finish, and disappear, where polling isn't possible.

Can I use a health check endpoint to monitor a cron job?

Not reliably, because a health check endpoint only confirms the host service is reachable, not that a specific scheduled job actually executed. A cron job can fail to run entirely while the server it lives on reports perfectly healthy. You need a check-in from the job itself to confirm the run happened.

What HTTP status code should a health check return?

A healthy service should return a 2xx status code, typically 200, often with a small JSON body confirming key dependencies are working. Failures should return 4xx or 5xx so load balancers and monitors can distinguish "not ready yet" from "something is broken."

Is a dead man's switch the same thing as health checking?

It's a variant of health checking built for cases where nothing is running to poll. Instead of a monitor checking in on a service, the dead man's switch waits for the job to check in on itself, and alerts when that check-in doesn't arrive on schedule.

How often should a health check run?

For always-on services, every 10 to 60 seconds is typical, balancing quick failure detection against unnecessary load. For heartbeat monitoring of scheduled jobs, the check-in frequency should simply match the job's own schedule, with a grace period sized to the job's normal run-time variance.

Do I need both uptime monitoring and health checking for scheduled jobs?

Yes — they cover different failure modes. Uptime and endpoint health checks confirm your infrastructure is reachable, while heartbeat-based check-ins confirm the actual scheduled work completed, and running both closes the gap either one leaves alone.