How-to & Playbooks8 min read

Monitoring Cron Jobs & Workers with Heartbeats

The backup you think is running might not be. Here's how a dead man's switch catches the jobs that fail without making a sound.

The Ensju team
On this page

Ask a room of engineers "is your backup running?" and watch the pause. Most backups, cron jobs, and queue workers run in the dark: no endpoint to check, no dashboard, no one watching. They work for months, then quietly stop — a changed path, a rotated credential, a full disk — and nobody finds out until the day the backup is needed and isn't there.

The short version

  • Scheduled jobs fail silently because there's nothing external to poll — normal uptime checks can't see them.
  • A heartbeat (dead man's switch) alerts you when an expected check-in doesn't arrive on time.
  • The job pings a unique URL on success; you alert when now > last ping + interval + grace.
  • Add optional start and fail pings for run duration and explicit failure detail.
  • It's the only reliable way to catch a job that never ran at all.

The silent-failure problem

Regular uptime monitoring works by reaching out: it requests your URL, connects to your port, resolves your DNS. That model breaks down completely for scheduled work, because a cron job has no door to knock on. It wakes up, does its thing, and exits. There's no service to poll, so there's nothing for a normal monitor to watch — and the most dangerous failure of all, the job that never ran, produces no error, no log line, no signal whatsoever.

What a heartbeat is

A heartbeat — also called a dead man's switch — inverts the direction of monitoring. Instead of you checking the job, the job checks in with you. Each run sends a small signal ("still alive, just finished"). The monitoring system knows how often that signal should arrive. When one is late, it alerts.

The elegance is in what it catches. A crashed job doesn't ping — caught. A job that was never scheduled doesn't ping — caught. A server that's powered off doesn't ping — caught. Silence itself is the alert.

How it works

The mechanics are simple:

  1. You create a heartbeat and get a unique ping URL — the URL is the credential, so treat it like a secret.
  2. You tell it the expected interval (e.g. "every hour") and a grace period.
  3. Your job requests that URL as its final step, but only on success.
  4. If no ping arrives within interval + grace, the heartbeat is marked overdue and you're alerted.

It never false-alarms on day one

A well-designed heartbeat starts its clock at the first ping, not at creation time. So setting one up doesn't immediately page you for a job that hasn't run yet — the countdown begins the first time the job checks in.

Choosing interval and grace period

Two numbers define a heartbeat, and getting them right is the whole game between false alarms and slow detection:

  • Interval — how often the job is supposed to check in. Match it to the schedule: hourly cron → 1 hour.
  • Grace period — how much lateness to tolerate before alarming. This absorbs normal run-time variance so a slightly slow run doesn't page you.

The rule: grace should cover the job's worst reasonable run time plus a small buffer.

JobIntervalGraceReasoning
Hourly sync (runs ~2 min)1 hour10 minAbsorbs a slow run without hiding a real miss
Nightly backup (runs ~40 min)24 hours2 hoursBackups vary with data volume
Every-5-min worker5 min3 minTight loop; catch stalls fast

Start and fail signals

A plain success ping is enough to catch silent death, but two optional signals make heartbeats far more useful:

The start ping

Ping a /start variant when the job begins and the success URL when it ends. The gap between them is your job's run duration — now you can catch a job that's still running but has slowed to a crawl, not just one that died.

The fail ping

If your job can detect its own failure, have it ping a /fail URL in that case. That turns "we heard nothing, maybe it's late" into "the job ran and explicitly reported it failed" — a stronger, faster signal you can route at higher severity.

What to monitor this way

  • Backups — database dumps, file snapshots, offsite syncs. The classic.
  • Cron jobs — cleanup tasks, report generation, cache warmers, renewals.
  • Queue & background workers — anything that should tick continuously.
  • Scheduled ETL / data pipelines — where a missed run corrupts downstream data.
  • Certificate renewal jobs — pair with expiry monitoring for belt and braces.

Setting one up

The whole integration is usually one line appended to a job. Add the ping as the last command so it only fires on success:

crontab — ping only if the backup succeeds
# Runs the backup, then pings on success (&& short-circuits on failure)
0 3 * * *  /usr/local/bin/backup.sh && curl -fsS -m 10 https://ensju.com/heartbeat/enshb_xxx > /dev/null

For a script where you want start, success, and fail signals:

backup.sh — start / success / fail
URL="https://ensju.com/heartbeat/enshb_xxx"

curl -fsS -m 10 "$URL/start" > /dev/null   # mark the run as begun

if /usr/local/bin/do-backup; then
  curl -fsS -m 10 "$URL" > /dev/null        # success
else
  curl -fsS -m 10 "$URL/fail" > /dev/null   # explicit failure
  exit 1
fi

Tip

Use curl -fsS -m 10: -f fails on HTTP errors, -ssilences the progress bar, -S still shows real errors, and -m 10caps the ping so a hung request never wedges your job.

Common mistakes

  • Pinging unconditionally. If you ping regardless of outcome (e.g. before the real work, or with ; instead of &&), a failing job still looks healthy. Ping after success.
  • Grace too tight. A grace shorter than the job's natural variance turns every slow run into a page. Start generous, tighten later.
  • Grace too loose. A 24-hour grace on an hourly job means a full day of silent failure before you hear about it. Match grace to the cost of a missed run.
  • Leaking the ping URL. The URL is the credential. Keep it out of public repos and rotate it if it's exposed.

Heartbeats are the missing half of monitoring — the part that watches what you can'treach. In Ensju they're a first-class resource (included on the free plan), with start/fail pings, per-job intervals and grace, and alerts through the same channels as everything else. For the outward-facing half, see what uptime monitoring is.

Frequently asked questions

What is a dead man's switch in monitoring?
It's a monitor that alerts you when an expected signal stops arriving. Instead of checking whether a service is up, it waits for a job to check in — and pages you if the check-in doesn't happen on time. It's the standard way to monitor cron jobs, backups, and background workers.
How do I monitor a cron job?
Give the job a unique ping URL and have it make a request to that URL as its last step when it succeeds. The monitoring system knows the expected interval; if a ping doesn't arrive within that interval plus a grace period, it alerts. Optionally ping a start URL first and a fail URL on error for richer detail.
Why can't I just monitor cron jobs with normal uptime checks?
Uptime checks reach into a service from outside. A cron job has no endpoint to reach — it runs, finishes, and exits. There's nothing to poll. Heartbeats invert the direction: the job reports to you, so a job that never runs (or dies silently) is exactly what gets caught.
What grace period should I use for a heartbeat?
Set the grace period to cover the job's normal run-time variation plus a small buffer — enough that a slightly slow run doesn't false-alarm, but short enough that a truly failed run is caught quickly. For a job that usually finishes in 2 minutes on an hourly schedule, a 5–10 minute grace is reasonable.

Get the next one in your inbox

Practical, vendor-neutral monitoring tips — what to watch and how to alert on it — plus the occasional Ensju update. No spam, unsubscribe anytime.

Monitoring tips + Ensju updates. No spam, unsubscribe anytime.