How to Get an Alert When a Cron Job Fails
Updated
Cron starts your jobs, but it doesn't tell you anything about how they went. A backup script can exit with an error every night, or never start at all because the server was rebuilt and the crontab wasn't, and nobody notices until the day someone needs the backup.
This guide shows how to get an alert for both kinds of failure: the job ran and failed, and the job didn't run. It takes one line per job.
Why MAILTO and log files are not enough
Classic cron can mail a job's output if you set MAILTO and the server has a working mail transfer agent. In
practice this breaks in three ways:
- Many servers and containers have no MTA, so the mail never leaves the machine.
- A job that prints nothing but exits with status 1 sends no mail at all.
- A job that never runs (cron daemon stopped, crontab overwritten, server gone) produces nothing to send.
Log files have the same blind spot: an absent entry doesn't raise an alarm. To catch every failure, the alert has to come from somewhere outside the server that expects to hear from the job.
How Cuckoo detects a failed cron job
Each job gets a check with a ping URL, https://cuckoo.metastic.ch/ping/YOUR-UUID, and a schedule: a period
(for example every hour) or a cron expression with a time zone, plus a grace time. Then:
- A ping to
/ping/YOUR-UUID(or/ping/YOUR-UUID/0) marks a successful run. - A ping to
/ping/YOUR-UUID/fail, or an exit code from 1 to 255 such as/ping/YOUR-UUID/1, marks a failed run and alerts right away. - No ping by the expected time plus grace means the job didn't run, and Cuckoo alerts as well.
- When the next successful ping arrives, you get a recovery message.
Alerts go to email (members with alert emails on), Slack, Discord and signed webhooks.
Step 1: Send the exit code with every run
Put the ping after the job, separated with ; so it runs whether the job succeeded or not, and pass $?:
# m h dom mon dow command
30 2 * * * /usr/local/bin/backup.sh; curl -fsS -m 10 --retry 5 -o /dev/null https://cuckoo.metastic.ch/ping/YOUR-UUID/$?
The shell expands $? before curl starts, so it is the exit status of backup.sh. Exit 0 is a success, anything
else is a failure and alerts immediately. If the network is down, curl retries five times and the job's own result
is unaffected.
Don't use && here: backup.sh && curl … only pings on success. That still catches failures, but only once the
grace time has passed, and you lose the exit code in the alert.
Step 2: Include the error in the alert
An alert that says "exit code 2" tells you something broke. An alert with the last lines of output tells you what. Cuckoo stores the first 10 KB of a POST body with the ping, so send the tail of the log:
#!/usr/bin/env bash
# /usr/local/bin/backup-monitored: wraps backup.sh and reports the result to Cuckoo
set -uo pipefail
LOG=$(mktemp)
trap 'rm -f "$LOG"' EXIT
curl -fsS -m 10 --retry 5 -o /dev/null https://cuckoo.metastic.ch/ping/YOUR-UUID/start || true
/usr/local/bin/backup.sh >"$LOG" 2>&1
rc=$?
tail -c 10000 "$LOG" | curl -fsS -m 10 --retry 5 -o /dev/null --data-binary @- \
"https://cuckoo.metastic.ch/ping/YOUR-UUID/$rc" || true
exit "$rc"
The /start ping is optional. With a max runtime set on the check, it also alerts when the job hangs: if no success
or failure arrives within the max runtime after /start, the check goes down.
set -o pipefail matters if your job is a pipeline such as pg_dump … | gzip > file. Without it, the exit status
is gzip's, and a failed dump looks like a success.
Step 3: Choose where the alert goes
In the dashboard, open Integrations and add a channel: a Slack or Discord webhook URL, or a generic webhook that receives a signed JSON payload (useful for PagerDuty-style tools, Microsoft Teams relays or your own bot). Email alerts go to every member who has them turned on. Alerts currently go to all integrations of the organisation, so use a separate organisation if a team needs its own routing.
Use Send test on each integration to confirm it arrives before you rely on it.
Step 4: Set a schedule and grace time that fit
Late alerts are only as good as the schedule. If the job runs at 30 2 * * * in Europe/Zurich, use the same cron
expression and time zone on the check, so daylight saving time changes don't cause false alarms. Set the grace
time to cover normal variation in run time: if a backup usually takes 5 to 20 minutes, a grace of 30 to 60 minutes
avoids noise without hiding a real miss for long.
Troubleshooting
- The check is "New" and never turns green. The ping isn't arriving. Run the curl line by hand as the cron user; on minimal images, curl may not be installed or may lack CA certificates.
- Failures don't alert, only late runs do. Check for
&&before curl, or a wrapper that always exits 0. - Alerts arrive for jobs that ran fine. The schedule on the check doesn't match the crontab, the time zone differs, or the grace time is shorter than the job's run time.
- Percent signs in the crontab. Cron treats
%as a newline; escape it as\%or move the command into a script.
Want to see it before you sign up? The live demo gives you a sandbox with a real ping URL to curl.
FAQ
How do I get notified when a cron job fails?
Append a ping with the exit code to the job: job; curl -fsS -m 10 https://cuckoo.metastic.ch/ping/YOUR-UUID/$?.
A non-zero code alerts right away by email, Slack, Discord or webhook, and a missing ping alerts after the grace time.
Can I get an alert when a cron job doesn't run at all?
Yes. This is the main reason to use a heartbeat monitor: Cuckoo expects a ping on schedule, so a job that never started (server down, crontab lost, cron daemon stopped) triggers an alert once the grace time has passed.
Does a failing ping break my job?
No, if you separate the job and the ping with ; and add || true in scripts. curl has a 10 second timeout and
retries, and the job's exit status is kept.
How much does it cost?
The Free plan includes 20 checks with every feature. Team is CHF 12/month for 200 checks and Business CHF 39/month for 1,000 checks.
Related guides
- Cron Job Start and Fail SignalsSend /start, success and /fail pings from cron jobs to measure run time and catch hung jobs. Includes a reusable bash wrapper script with trap and exit codes.
- Cron Schedule, Time Zone and Grace PeriodPick the right monitoring schedule for a cron job: period or cron expression, IANA time zones, how cron handles daylight saving time, and sizing grace time.
- Dead Man's Switch Monitoring ExplainedWhat dead man's switch monitoring is, how push-based heartbeat checks differ from pull-based uptime monitoring, and which jobs and systems benefit from each.
- How to Monitor Cron JobsLearn how to monitor cron jobs with heartbeat pings: why cron fails silently, how a dead man's switch catches missed runs, and how to set up a first check.