How to monitor cron jobs
Cron does not tell you when a job stops running. A backup can fail every night for weeks before anyone looks. This guide shows the heartbeat pattern, which works with any monitoring tool and any language.
Why normal monitoring misses cron jobs
- A site check asks "is the server up?" A cron job can fail while the server is fine.
- When a job never starts (crontab deleted, server rebuilt, timezone wrong), there is no error to log.
- Cron's own email (
MAILTO) only works if mail is set up on the server and someone reads it.
The heartbeat pattern (dead man's switch)
Turn it around: instead of watching for errors, expect a signal. Each time the job finishes, it calls a unique URL (a "ping"). The monitor knows the schedule. If no ping arrives in time, it alerts you. Silence is the alarm, so it also catches jobs that never ran.
Step 1: Create a check with the job's schedule
In your monitoring tool, create a heartbeat check and give it the same schedule as the job, for example 0 3 * * * (every day at 03:00), and the same timezone the server uses. You get a unique ping URL. Treat it like a password: anyone with it can send pings.
Step 2: Ping at the end of the job
Add the ping after the job, joined with &&, so it only runs when the job succeeds:
# m h dom mon dow command
0 3 * * * /opt/scripts/backup.sh && curl -fsS -m 10 --retry 3 -o /dev/null https://monitor.example/ping/YOUR-TOKEN
-ftreats HTTP errors as failures,-sShides progress but shows errors.-m 10stops curl from hanging the job if the network is slow.--retry 3retries short network blips.
Inside a script, ping as the last line, after set -e so any failed step stops the script before it pings:
#!/usr/bin/env bash
set -euo pipefail
pg_dump mydb | gzip > /backups/mydb-$(date +%F).sql.gz
curl -fsS -m 10 --retry 3 -o /dev/null https://monitor.example/ping/YOUR-TOKEN
Step 3: Report start and failure too (optional)
Many tools accept extra signals. A "start" ping lets the monitor measure how long the job runs and notice a job that hangs. A "fail" ping alerts you right away instead of waiting for the deadline:
URL=https://monitor.example/ping/YOUR-TOKEN
curl -fsS -m 10 -o /dev/null "$URL/start"
if /opt/scripts/backup.sh; then
curl -fsS -m 10 -o /dev/null "$URL"
else
curl -fsS -m 10 -o /dev/null "$URL/fail"
fi
Step 4: Pick a grace period
The grace period is how long past the expected time the monitor waits before it alerts. Too short and you get false alarms; too long and you hear late.
- Set it a bit longer than the job's normal run time, plus some slack. A backup that takes 20 minutes might get 30 to 45 minutes.
- For jobs that run every minute or every five minutes, a few minutes is usually enough.
- If the run time grows over months (bigger database), raise the grace period before it starts to page you.
Step 5: Send alerts where people will see them
- Send alerts to a channel your team already reads (chat, SMS or email), not to an inbox nobody opens.
- Alert on recovery too, so people know when it is fixed.
- For important jobs, use escalation: if nobody acknowledges, alert the next person.
Step 6: Test it
Run the ping by hand once to see it arrive. Then break it on purpose: comment out the job, or change the ping URL, and wait for the alert. A monitor you have never seen fire is one you can't trust.
curl -fsS -m 10 https://monitor.example/ping/YOUR-TOKEN && echo "ping sent"
The same pattern outside cron
- systemd timers: add the curl as an
ExecStartPost=line, or put it at the end of the script. - Kubernetes CronJobs: ping as the last command in the container.
- Queue workers and loops: ping every N minutes from inside the loop, and set the check to that interval.
- Windows Task Scheduler: use
curl.exe(built into Windows 10 and later) or PowerShellInvoke-WebRequest.
Doing it with vizsu
Uptime by vizsu has heartbeat checks. You give the check a cron expression, a timezone and a grace period (at least 30 seconds), and Uptime gives you a ping URL that ends in /heartbeat/<token>. Copy it from the check.
curl -fsS -m 10 https://<your ping URL> # success
curl -fsS -m 10 https://<your ping URL>/start # job started
curl -fsS -m 10 https://<your ping URL>/fail # job failed, alert now
You can also POST to <your ping URL>/<exit code>: 0 counts as success, anything else as a failure. A missed or failed ping opens an incident and alerts your channels, with escalation if nobody acknowledges.
Questions
What is a dead man's switch for cron jobs?+
The job sends a ping to a monitor every time it finishes. If the monitor does not get a ping on schedule, it alerts you. It catches jobs that fail and jobs that never start.
Why put the curl after && instead of on its own line?+
With && the ping only runs when the job exits with code 0. On its own line, the ping would be sent even when the job failed.
How long should the grace period be?+
A little longer than the job's normal run time plus some slack. For a 20-minute backup, 30 to 45 minutes is a sensible start.
Will a failing ping break my job?+
Use curl -m 10 so it can't hang. Because the ping is the last step, a failed ping does not undo the job's work; the monitor will alert you that the ping is missing.
Does this work for systemd timers and Kubernetes CronJobs?+
Yes. Anything that can make an HTTP request can send a heartbeat.
Related
Cron and heartbeat checks · Uptime · Business email on your own domain · All guides