A dead man's switch in monitoring is a check that alerts you when something stops happening, rather than when something goes wrong. Your backup job calls a URL every night after it finishes. The monitor expects that call. If it does not arrive inside a grace window you set, you get paged. The name comes from the safety devices on trains and industrial machines: the operator has to keep holding the lever, and if they let go, whether from a heart attack or a nap, the machine stops on its own. Silence is the alarm.
This inverts how monitoring normally works, and that inversion is the whole point. A regular uptime check goes out and asks your server if it is alive. A dead man's switch sits still and waits for your job to report in. The first one catches things that are broken. The second one catches things that have quietly ceased to exist, which is a category of failure that is otherwise almost invisible.
Why the inversion matters
Think about how a cron job actually fails. The optimistic version is that it throws an error, exits non-zero, and emails you. That does happen and it is the easy case.
The realistic version is quieter. Someone edits the crontab and fat-fingers a line, so the job never fires again. The server it ran on gets rebuilt and the entry does not survive. The cron daemon dies and nobody notices because nothing else depends on it. The user account the job ran as gets disabled during an offboarding. The disk fills and cron cannot write. In every one of those, your job produces no error, because your job does not run. There is nothing to produce an error. From the outside it looks exactly like a job that succeeded quietly, which is what a well-behaved job looks like anyway.
This is the failure that gets discovered at the worst possible moment. The backups have not run since March. You find out in September, when you need one.
the asymmetry
A job that errors tells you it errored. A job that never runs tells you nothing at all, and “nothing at all” is indistinguishable from success. A dead man's switch is the only mechanism that turns absence into a signal.
How it works, concretely
The mechanics are almost insultingly simple, which is part of why the pattern is so durable.
- You create a monitor and tell it the schedule you expect: every 24 hours, every hour, every 5 minutes.
- You set a grace time, the amount of lateness you will tolerate before it counts as a failure. A nightly backup that usually takes 20 minutes might get 60 minutes of grace.
- The monitor gives you a unique URL.
- Your job calls that URL when it succeeds. One line at the end of the script.
- If the call does not arrive within the period plus the grace time, the monitor alerts you.
The simplest possible version, appended to a crontab line:
# run the backup, ping only if it exits 0
0 3 * * * /usr/local/bin/backup.sh && curl -fsS -m 10 https://ping.alertping.com/<uuid>
The && is doing the important work. It means the ping only fires if
backup.sh exits zero. A job that runs and fails does not ping, so it alerts, same as
a job that never runs at all. Both failure modes collapse into one signal, which is exactly what
you want at 3am.
The -f and -m 10 flags matter more than they look. -f
makes curl fail on an HTTP error instead of cheerfully printing the error body and exiting zero.
-m 10 caps the whole thing at ten seconds so a network hiccup on the monitoring
side can never hang your backup job. Never let the monitoring be the thing that breaks the
thing it monitors.
Where a dead man's switch is the right tool
| Job | Expected period | What silence means |
|---|---|---|
| Nightly database backup | 24 hours | You have no recovery point. The worst one to miss. |
| Certificate renewal | Weekly attempt | An expiry is coming that nobody is watching. |
| ETL or warehouse load | Hourly or daily | Dashboards are quietly showing stale numbers. |
| Invoice or payout run | Daily or monthly | Money did not move. Someone will notice eventually. |
| Queue worker heartbeat | Minutes | Jobs are piling up unprocessed. |
| Log rotation or cleanup | Daily | A disk is filling up toward an outage. |
| Data export to a partner | Daily | A contractual obligation is silently unmet. |
The pattern applies well beyond cron. Anything periodic works: a Kubernetes CronJob, a Lambda on a schedule, a Sidekiq or Celery worker, a GitHub Action, a Windows scheduled task, a systemd timer. If it is supposed to happen on a rhythm, its silence can be made to mean something.
The mistakes people make
Pinging at the start instead of the end
If you ping when the script begins, you are monitoring that cron fired, which is the smaller half of the problem. Ping at the end, and only on success. A job that starts and dies halfway through is a failure, and a start-ping cheerfully reports it as healthy.
Grace times that are too tight
A backup that normally takes 20 minutes will one day take 90 because the database grew. If your grace is 25 minutes, you get paged for a job that is working fine. Two or three of those and the team starts ignoring the alerts, at which point you have spent your credibility and gained nothing. Set grace generously at first and tighten it once you know the real distribution of run times.
Assuming the ping proves the work was correct
This is the subtle one, and it is where the pattern's limits show. A dead man's switch tells you
the job ran and exited zero. It does not tell you the job did anything useful. A backup script
can exit zero having written a 0-byte file. An ETL job can exit zero having loaded an empty
result set because an upstream API returned [] instead of erroring.
So put the assertion inside the script and let the exit code carry real meaning: check the dump is over some plausible size, check the row count is not zero, and exit non-zero if it is not. Then the heartbeat means something. For pipelines feeding a warehouse this is worth taking further, since “the job ran” and “the numbers are right” are genuinely different claims. Teams that care about the second one track freshness and volume of the tables themselves rather than trusting the loader's exit code.
Only monitoring the job, not the thing it protects
A heartbeat on the backup script is good. It is also blind to the fact that the server holding the backups is unreachable, or the certificate on the API expired, or the site is down. Those need outbound probing, which is a different kind of check entirely, and running the two in separate tools is how gaps appear. It is worth reading synthetic monitoring versus real user monitoring for where each type of check earns its place.
Dead man's switch vs regular uptime monitoring
| Dead man's switch | Uptime check | |
|---|---|---|
| Direction | Your job calls the monitor | The monitor calls your server |
| Alerts on | Absence of a signal | A bad or missing response |
| Best for | Cron, backups, queues, scheduled work | Websites, APIs, ports, certificates |
| Blind to | Whether the work was correct | Anything with no public endpoint |
| Works behind a firewall | Yes, it only makes outbound calls | No, it needs to reach you |
That last row is why the pattern exists at all for internal infrastructure. A batch job on a box with no inbound access cannot be probed by anything. It can still curl out. The dead man's switch is often the only monitoring such a machine can have.
Most teams need both kinds, which is the practical argument for keeping them in one place. A heartbeat-only tool cannot tell you your website is down; a probe-only tool cannot tell you your backup stopped. If you are currently running one of each, the Healthchecks.io alternative and Cronitor alternative comparisons lay out the tradeoff honestly, including the cases where staying put is the right call.
Setting one up
Realistically this is a ten-minute job for your most important scheduled task, and the backup is
almost always the right place to start. Create a heartbeat monitor, set the period and a
generous grace time, take the URL, and append the curl to your crontab with
&&. Then, and this is the step everyone skips, break it on purpose. Comment
out the cron line and wait for the alert. An untested dead man's switch is a rumor.
The full walkthrough with more shell examples is in how to monitor a cron job, and the feature itself is cron job monitoring. On AlertPing a heartbeat counts as an ordinary monitor, so it comes out of the same allowance as your website checks and lands in the same escalation policy, which means the backup job and the site it protects finally page the same person.
The short version
A dead man's switch turns silence into an alert. It is the only practical way to catch a scheduled job that stopped running, because a job that does not run cannot report its own failure. Ping at the end and only on success, be generous with grace time, make the exit code mean something real, and test it by breaking it. Then pair it with outbound checks on the things that job exists to protect, because neither kind of monitoring sees what the other one sees.