How to monitor cron jobs and get alerted when they fail
Updated 1 November 2026
Cron starts each command on time and forgets about it. It doesn’t tell you when a job fails, and when a job stops running, because the server was off, cron was stopped or someone replaced the crontab, it says nothing at all. Here are the ways to find out, and what each one misses.
What can go wrong
- The job fails. It runs and ends with an exit code other than 0 (what each code means is in cron job exit codes).
- The job doesn’t run. Cron is stopped, the server is down, the crontab was overwritten, or the schedule or the time zone isn’t what you think.
- The job hangs. It starts and never ends, or takes much longer than usual.
- Two runs overlap. A run starts while the previous one is still running (how to avoid it is in how to prevent overlapping cron jobs).
A good method catches the first three. Most of them only catch the first.
MAILTO: cron’s email
Cron emails what a job prints, to the address in MAILTO:
MAILTO=ops@example.com
0 3 * * * /path/to/backup.sh
-
It needs a mail server on the machine, like Postfix or msmtp, that can deliver to your address. Most servers and containers don’t have one: on Debian and Ubuntu, cron logs
No MTA installed, discarding outputand the email is lost. - It emails when the job prints something, not when it fails. A job that prints on every run fills your inbox and gets ignored; a job that fails without printing anything sends nothing.
To get an email only when the job fails, keep the output in a file, and when the job fails, print a line with its exit code and then the output:
MAILTO=ops@example.com
0 3 * * * /path/to/backup.sh > /tmp/backup.log 2>&1 || { echo "backup.sh failed with exit code $?"; cat /tmp/backup.log; }
The echo line makes sure there’s an email even when the job fails without printing anything. It still can’t tell you that the job didn’t run: no run, no output, no email. How to make the emails arrive, step by step, is in how to get an email when a cron job fails.
Logs
The system log shows when cron started each command, but not how it ended. To also have each run’s output and exit code, the job has to write them to its own log, as shown in cron logs.
Logs help to investigate a run, but they don’t alert: you only find the problem when you look, usually after someone notices that the backup from last week isn’t there.
An alert from the job itself
The crontab line can send an alert when the job fails, for example to a Slack incoming webhook:
0 3 * * * /path/to/backup.sh || curl -fsS -m 10 -H 'Content-Type: application/json' -d '{"text":"backup.sh failed"}' https://hooks.slack.com/services/...
It alerts on failures right away, with no mail server. But like MAILTO, it depends on the job running: if cron or the server is down, nothing sends the alert, and silence looks the same as success.
Heartbeats
A heartbeat is a signal that your job sends to a monitoring service each time it runs. The service expects it within a set time and alerts you when it doesn’t arrive. Since the alert comes from another machine, you’re told even when the server is off, cron is stopped or the crontab is gone. This is also called a dead man’s switch.
What to look for, in a service or in your own setup:
-
Send the exit code, not only that the job ran. With
backup.sh && curl ..., a failure is only noticed when the time to report runs out; sending the code with$?alerts right away, with the code. - Send the start too. With a signal at the start and another at the end, the service can tell a job that hangs from one that didn’t start, measure how long each run takes and see two runs overlap.
- Choose how long to wait. A job that sometimes takes 40 minutes needs more margin than one that takes seconds.
-
Don’t let the monitoring break the job. Call it with a timeout, like
curl -m 10, so a slow network doesn’t hold the job. - Alerts that don’t flood you. A job that fails every minute shouldn’t send an email every minute.
You can build your own, with a script on another server that checks a file the job touches, but then that script needs monitoring too. That’s why most people use a service.
Which one to use
| Method | Job fails | Job doesn’t run | Job hangs |
|---|---|---|---|
| MAILTO | If mail works, with the line above | No | No |
| Logs | Only when you look | Only when you look | Only when you look |
| Alert from the job | Yes | No | No |
| Heartbeat with start, end and exit code | Yes | Yes | Yes |
Logs and MAILTO are worth keeping to investigate a run. To also find out when a job stopped running, use a monitor that expects a signal within a set time.
Services
These services receive heartbeats and alert you when a job fails or doesn’t report in time. All of them have a free plan:
- Healthchecks.io: open source (BSD), so you can also host it yourself.
- Cronitor: cron jobs, heartbeats, website and API checks, and status pages.
- Dead Man’s Snitch: alerts when a scheduled task doesn’t run when it should.
- Better Stack: cron monitoring as part of a platform for uptime, incidents and logs.
- CronDrive, this site: signals at the start and at the end with the exit code, alerts by email, Slack or webhooks (Discord, Microsoft Teams, Telegram, PagerDuty or your own URL), overlapping runs, and an MCP server so that an AI agent can set up a server’s crontab.
If you need to keep everything on your own servers, Healthchecks.io is the one to look at.
Monitor a cron job with CronDrive
Add a call before and after the command, with the exit code:
0 3 * * * curl -fsS -m 10 https://job.crondrive.com/<token>/start/$$; /path/to/backup.sh; curl -fsS -m 10 https://job.crondrive.com/<token>/end/$?/$$
$? is the exit code of the job, and $$ a number that pairs each start with its end. You don’t have to write it: create the job in CronDrive with the schedule from your crontab, and it gives you this line with the job’s own token in place of <token>. Paste it in place of the old one and turn on monitoring; the next run shows up in the job’s history. More in the docs.
CronDrive alerts you when the code isn’t 0, or when a run doesn’t start or doesn’t finish in the minutes you allow. If several runs in a row go wrong, you get the first three alerts, and then nothing until a run goes well, when you get a notice that the job works again.