Silent Cron Job Failures — Why You Need Heartbeat Monitoring
Here's an uncomfortable question for anyone running scheduled jobs: if your nightly backup stopped running three weeks ago, how would you know? For most teams the honest answer is "when we need the backup." Cron job monitoring exists because cron's failure mode is silence — no crash, no error page, no alert. The job just stops happening, and everything looks fine until the day it very much isn't. This post covers why cron failures are invisible by design, the failure modes that actually bite, and how heartbeat (dead-man's-switch) monitoring turns silence into an alert.
Why cron failures are silent by design
Cron is a 1970s design that assumes someone is reading the mail. Its entire error-reporting story is:
- Exit codes nobody reads. A job exits non-zero; cron notes it and moves on. There is no retry, no escalation, no log entry your team watches. The exit code evaporates.
- Mail nobody receives. Cron's one notification channel is local email to the crontab's owner via
MAILTO. On a modern server there's usually no MTA configured at all — or mail goes toroot@localhost, a mailbox that has never once been opened. Even where mail delivery works, cron only mails when a job produces output, and plenty of failing jobs fail silently with none.
The deeper problem is structural: cron can only report that a job it ran misbehaved. It cannot report that a job didn't run at all — and "didn't run" is precisely the failure you care about most.
The failure modes that actually happen
These are the ways scheduled jobs die in real life, and almost none of them produce any signal:
- The server rebooted and something didn't come back. The box restarted after a kernel patch; the job that was mid-flight never re-ran, or a service the job depends on didn't start.
- The cron daemon itself is dead. Rare but devastating: crond crashed or was never enabled after a migration. Every scheduled job on the machine silently stops. Nothing errors, because nothing runs.
- The job hangs forever. A backup blocks on a stale NFS mount or an unanswered network call. It never exits, so it never even produces a failing exit code — and depending on locking, it may block every subsequent run too.
- The crontab was edited or lost. A commented-out line during debugging that never got uncommented; a provisioning tool that rebuilt the machine without the crontab; a deleted deploy user taking their crontab with them.
- DST edge cases. Jobs scheduled between 1 and 3 AM local time can be skipped on spring-forward or double-run on fall-back, depending on your cron implementation. Some handle it gracefully; many setups don't. The safe moves: schedule in UTC, or keep jobs out of the 1–3 AM local window.
- The job runs but the work fails. The backup script executes and exits 0 — but the dump was empty, or the upload to object storage failed inside a
||branch someone wrote to "keep it from erroring."
Notice the pattern: uptime monitoring catches none of these. Your website is up; it's the invisible machinery behind it that's dead.
How heartbeat monitoring works
Heartbeat monitoring — also called a dead man's switch — inverts the reporting model. Instead of asking cron to report failure (which it can't do reliably), each job actively reports success:
- Each job gets a unique ping URL.
- On successful completion, the job requests that URL.
- The monitor knows the job's schedule and a grace window. If the expected ping doesn't arrive in time, it alerts.
The elegance is that this catches every failure mode above with one mechanism. Server rebooted, crond dead, job hung, crontab deleted, script crashed — from the monitor's perspective these are all the same event: the ping didn't arrive. You don't need to enumerate the ways a job can die; silence itself becomes the alarm.
A good heartbeat monitor also tracks when pings arrive, catching jobs that still run but have drifted off schedule or started taking 4 hours instead of 10 minutes.
Instrumenting your crontab
Before — the classic silent liability:
0 3 * * * /usr/local/bin/backup.sh
After — pings only when the job actually succeeds:
0 3 * * * /usr/local/bin/backup.sh && curl -fsS --retry 3 -m 10 https://hb.example.com/ping/a1b2c3 > /dev/null
The details matter:
&&ensures the ping fires only on exit code 0. If the script fails, no ping, and the missed window raises the alert.-fmakes curl treat HTTP errors as failures;-sSstays quiet except on real errors;--retry 3 -m 10rides out transient network blips without hanging your job.- Ping at the end, not the start. A ping fired before the work certifies nothing. (Monitors that support start and success pings can also measure duration and catch hangs mid-run.)
For a job wrapped in a script, ping from inside after verifying the work — for a backup, that means checking the dump is non-trivial before declaring victory:
#!/usr/bin/env bash
set -euo pipefail
pg_dump myapp | gzip > /backups/myapp-$(date +%F).sql.gz
test "$(stat -c%s /backups/myapp-$(date +%F).sql.gz)" -gt 1000000
curl -fsS -m 10 --retry 3 https://hb.example.com/ping/a1b2c3 > /dev/null
set -euo pipefail means any failing step aborts the script before the ping line — the heartbeat only fires when every step, including the sanity check, passed.
Set the grace window realistically: a nightly job that takes 5–20 minutes might get a 30-minute grace period. Too tight and you get noise; too loose and you find out late. And instrument the jobs that matter first: backups, billing runs, queue workers' scheduled tasks, certificate renewal (yes — renewals fail silently too), data syncs, report generation.
Silence should page someone
Cron's contract is "I'll run it and tell no one." Heartbeat monitoring rewrites that contract: every scheduled job proves it ran, and silence becomes an alert instead of a time bomb. CompleteStatus includes cron/heartbeat monitoring alongside uptime, SSL, DNS and security monitoring in one dashboard — each job gets a ping URL, a schedule and a grace window, and a missed window alerts you by email, Slack, Discord, Telegram or webhook. It's the tool most teams bolt on as a separate $49/mo subscription (as of May 2026) after their first silent failure; here it's bundled with everything else, including on the free tier.
Create a free account — 10 monitors, commercial use allowed — and give your backup job a heartbeat today. It's one curl appended to a crontab line, and it's the difference between finding out tonight and finding out during a restore.