CronDrive
Guides

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 output and 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.

CronDrive emails you, or posts to Slack, when a job with this schedule fails, or doesn't start or finish on time. Free for up to 15 jobs.

What you'd see with this schedule

An example with the last runs of this schedule and made-up results: one failed and one didn't run.

Times in UTC.

Due
Sun 1 Nov, 03:00 On time Started 03:00, finished 03:13, took 13 min.
Sat 31 Oct, 03:00 Didn't run Alerts sent at 03:05 and 03:30.
Fri 30 Oct, 03:00 On time Started 03:00, finished 03:14, took 14 min.
Thu 29 Oct, 03:00 Failed with code 1 Alert sent at 03:04. Started 03:00, finished 03:04, took 4 min.
Wed 28 Oct, 03:00 On time Started 03:00, finished 03:13, took 13 min.
Tue 27 Oct, 03:00 On time Started 03:00, finished 03:14, took 14 min.

The alert of the failed run

Email from CronDrive, Thu 29 Oct, 03:04

Failed (exit code 1): "Your job"

The job "Your job" failed with exit code 1.

If you connect Slack, the same message goes to your channel.

The change in your crontab

0 3 * * * curl -fsS -m 10 https://job.crondrive.com/uG2Exw/start/$$; /path/to/job.sh; curl -fsS -m 10 https://job.crondrive.com/uG2Exw/end/$?/$$

Two calls around your command, in the same line. Your job's line comes with its own token.

Monitor this schedule