How to Monitor Cron Jobs
Updated
Cron is reliable at one thing: starting commands on a schedule. It is not good at telling you when those commands stop working. A nightly backup can fail for weeks before anyone looks, because cron has no idea what "success" means for your job, and the default failure notification (local mail) usually goes nowhere.
This guide explains why cron jobs fail silently, how heartbeat monitoring catches it, and how to get a first check running in about five minutes.
Why cron jobs fail silently
Common reasons a scheduled job stops doing its work without anyone noticing:
- The job never runs. The cron daemon is stopped, the server was replaced, the crontab was overwritten during a deploy, or a container that used to run cron no longer exists.
- The job runs but fails. A credential expired, a disk is full, an API changed, or
PATHinside cron differs from your interactive shell, so a command is "not found". - The job hangs. A network mount stalls or a lock is never released, and the process sits there forever.
- The output goes nowhere. Cron mails output to the crontab owner via
MAILTO, but many servers have no working mail transport, so the message is dropped or ends up in a local mailbox nobody reads.
Log files and exit codes exist, but somebody has to look at them. Monitoring needs to work the other way around: it should tell you when something is wrong.
The heartbeat approach
Cuckoo monitoring (also called a dead man's switch) flips the responsibility. Your job sends a small HTTP request, a "ping", every time it finishes successfully. Cuckoo knows how often to expect that ping. If it does not arrive within the expected period plus a grace time, the check goes down and you get an alert.
That single mechanism covers all the failure modes above: a job that never starts, crashes, or hangs simply never sends its success ping. You do not need an agent on the server or inbound firewall rules, only outbound HTTPS.
Cuckoo understands a few signals on the same URL:
| URL | Meaning |
|---|---|
https://cuckoo.metastic.ch/ping/YOUR-UUID |
Run finished successfully |
https://cuckoo.metastic.ch/ping/YOUR-UUID/start |
Run started (enables duration and hang detection) |
https://cuckoo.metastic.ch/ping/YOUR-UUID/fail |
Run failed, alert now |
https://cuckoo.metastic.ch/ping/YOUR-UUID/1 |
Exit code; 0 = success, anything else = failure |
Requests can be GET, HEAD or POST. With POST, the first 10 KB of the body (UTF-8 text) is stored with the ping, which is handy for attaching the tail of a log.
Your first check in five minutes
1. Create a check
Sign up at https://cuckoo.metastic.ch and create a check. Give it a name like nightly-backup and choose a
schedule. Either pick a simple period ("every 1 day") or paste the same cron expression the job uses, for example
30 2 * * *, together with the server's time zone such as Europe/Zurich. Set a grace time, for example 30 minutes.
2. Copy the ping URL
Each check has its own URL of the form https://cuckoo.metastic.ch/ping/YOUR-UUID. Treat it like a password: anyone
who has it can send pings for that check.
3. Add the ping to your crontab
Run crontab -e and append the ping to the job with &&, so it is only sent when the job succeeds:
30 2 * * * /usr/local/bin/backup.sh && curl -fsS -m 10 --retry 5 -o /dev/null https://cuckoo.metastic.ch/ping/YOUR-UUID
The curl flags matter: -f makes HTTP errors count as failures, -sS hides the progress bar but keeps errors,
-m 10 caps each attempt at ten seconds, --retry 5 retries transient network errors, and -o /dev/null discards
the response body so cron does not mail it to you.
4. Test it
Run the same command by hand once. The check should show a fresh ping in the dashboard. Then configure where alerts should go: email, Slack, Discord or a signed webhook.
5. Report failures explicitly (optional)
Waiting for a missed ping means you learn about a failure only after the grace time. To be alerted right away, report the exit code instead:
30 2 * * * /usr/local/bin/backup.sh; curl -fsS -m 10 --retry 5 -o /dev/null https://cuckoo.metastic.ch/ping/YOUR-UUID/$?
A non-zero exit code marks the run as failed immediately.
Choosing schedule and grace time
Match the check to how the job is actually scheduled. A cron expression with the correct time zone is the most precise option for jobs at fixed times. For jobs that run "roughly every hour", a period is simpler.
The grace time should cover the normal variation in run time plus some slack. A five-minute job that runs at 02:30
might get a 20 to 30 minute grace time. A backup that takes anywhere from one to three hours needs more, or better,
a /start ping plus a max runtime, so a hung job is caught without making the grace time huge.
Troubleshooting
- The ping works by hand but not from cron. Cron runs with a minimal environment. Use absolute paths
(
/usr/bin/curl) or setPATHat the top of the crontab. - The check shows late although the job ran. Compare the check's time zone with the server's. A cron expression
evaluated in UTC versus
Europe/Zurichis off by one or two hours. - Pings fail behind a proxy. curl honours
https_proxy; set it in the crontab if outbound traffic needs a proxy. - Nothing arrives at all. Check
grep CRON /var/log/syslogorjournalctl -u cron(cronieon some distributions) to confirm cron started the job.
FAQ
What happens if the Cuckoo service cannot be reached during a ping?
curl retries up to five times. If every attempt fails, the job itself is not affected, because the ping runs after the job. The check may then go late and alert you, which is the safe direction for a monitoring signal.
Do I need to install an agent on my server?
No. All you need is a tool that can make an outbound HTTPS request, such as curl, wget, or an HTTP client in your programming language. Nothing listens for inbound connections.
Should I ping at the start or at the end of a job?
Always ping at the end, after the work succeeded. Add a /start ping at the beginning if you also want to measure
duration and detect jobs that hang.
Can I monitor jobs that are not cron jobs?
Yes. Anything that runs on a schedule and can send an HTTP request works, including systemd timers, Kubernetes CronJobs, CI pipelines, and scheduled tasks inside applications.
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.
- Set Up Cuckoo Alerts: Email, Slack, Discord and WebhooksSet up Cuckoo alert channels: email, Slack and Discord webhooks, signed generic webhooks with HMAC verification, retries, and how to avoid alert fatigue.