Cron job monitoring best practices
A practical checklist for monitoring cron jobs: report outcomes, watch for the run that never happened, set realistic grace periods, and alert on the right signal.
Most cron jobs run unattended for months, and most cron monitoring is an afterthought bolted on once a job has already failed in production. The good news is that doing it well is not complicated. A handful of habits cover almost every way a scheduled job goes wrong.
Here is the checklist I apply to every job that matters.
Monitor the outcome, not the exit code
A job that exits 0 is not the same as a job that did its work. A backup script can finish cleanly while writing a zero-byte archive. A sync can succeed while skipping every row because an upstream API returned an empty list.
Decide what success actually means for the job and check that, not just the exit status. Did the file land and is it the size you expect? Did the row count change? Send the success signal only after that assertion passes, so a green check means the work happened.
0 2 * * * /usr/local/bin/backup.sh && \
test -s /backups/$(date +\%F).tar.gz && \
curl -fsS https://ping.cronaut.dev/your-check-id
The test -s means the ping only fires when the backup file exists and is non-empty.
Watch for the run that never happened
This is the failure most setups miss. If a job stops being scheduled, gets commented out during a deploy, or the box it runs on dies, nothing errors. There is no output and no non-zero exit, because nothing ran.
You cannot catch this from inside the job. You need something external that knows the schedule and alerts when an expected run does not check in. Give the monitor your cron expression, let it compute the next expected ping, and have it page you when that ping is late. This flips monitoring from “tell me when the job fails” to “tell me when the job goes silent,” which covers a much bigger class of problems.
Set grace periods that match reality
A job that normally takes two minutes will occasionally take twelve because the database was busy or the network was slow. If your deadline has no slack, you get paged for a job that was fine.
Set the grace period to cover a normal slow run, then let anything past that alert you. Look at the actual run-duration history rather than guessing. Pair this with flap detection so a job that misses one window and recovers on the next does not wake you up at 3am for nothing.
Track start and end, not just success
Pinging only on success tells you the job finished. Pinging at the start as well tells you it began, how long it ran, and lets you tell a crash apart from a job that never started.
0 2 * * * curl -fsS https://ping.cronaut.dev/your-check-id/start && \
/usr/local/bin/backup.sh && \
curl -fsS https://ping.cronaut.dev/your-check-id
Now a missing start ping means the job never launched, and a start with no matching success means it died partway through. Those are different bugs and you want to know which one you have before you open a terminal.
Make jobs safe to retry
Monitoring tells you something broke. The next thing you will do is rerun the job, so write it to survive that. Idempotent jobs that you can rerun without double-charging a customer or duplicating rows turn an incident into a one-line fix. Use a lock or a claim so two overlapping runs do not stomp on each other when a slow run overlaps the next scheduled one.
Capture output and keep a history
When a job fails you want the logs from that run, not a live rerun that might behave differently. Redirect stdout and stderr to a file or your log pipeline, and keep enough history to see whether a failure is new or a job has been flaky for weeks.
0 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
A monitor that stores each run’s status and duration over time gives you the same history without touching the box, which is usually faster when you are trying to answer “when did this start failing.”
Alert on the right signal, in the right place
An alert nobody sees is not monitoring. Route cron failures to wherever you already look, a Slack channel, email, or your on-call tool, and make the message say which job, on which host, and how late. Avoid alerting on every individual failure of a noisy job; alert on the state change from healthy to failing so you get one clear page instead of a flood.
Test the monitoring itself
Silently broken monitoring is worse than none, because you trust it. Once a check is set up, break the job on purpose, let a window pass, and confirm you actually get paged. Do this again whenever you change the schedule or the alert routing. A check you have never seen fire is a check you should not rely on.
Putting it together
Good cron monitoring comes down to a few ideas: confirm the work happened, catch the run that never started, give jobs realistic slack, and make sure the alert reaches a human. None of it needs a heavy platform.
Cronaut is built to make this the default. You give it the schedule, add a start and success ping to the job, and it handles the deadline tracking, grace periods, flap detection, run history, and alerting. When a job goes silent it opens an incident and pages you, and the same engine watches your HTTP and SSL checks so everything that can break shows up in one place. See cron job monitoring for how it works, or read how to know if a cron job actually ran for the failure this is designed to catch.