Cron Job Monitoring: How to Know When a Scheduled Job Fails
July 2026 · Uptimehub
Checked from regions with auto-retry. No single-location false alarms.
All systems operational. Steady pulse across every region.
api.example.com returned no response from all 6 regions. Auto-retry confirmed the outage, then we alerted your team.
api.example.com is back up. The incident is logged to your status page history automatically.
90-day uptime · branded · your domain
Live demo · drive it, no signup needed
Cron job monitoring is the practice of confirming that a scheduled job actually ran, finished, and finished on time, instead of assuming it did. It works with a heartbeat: the job sends an HTTP request to a monitoring URL when it completes, and if that request does not arrive inside the window you expect, the monitor alerts you. This catches the failure mode cron itself cannot report, which is a job that never started at all. Standard error handling only tells you about jobs that ran and crashed; a heartbeat check is what tells you about the ones that silently stopped running.
Why cron jobs fail silently
Cron has no idea what your job is supposed to do. It fires a command at a time and moves on. If the command exits with an error, cron will usually mail the output to a local mailbox nobody reads. If the crontab entry got wiped during a deploy, if the server rebooted and the service never came back, if the disk filled up so the interpreter could not start, or if someone commented the line out six weeks ago while debugging, cron reports nothing at all, because from cron's perspective there is nothing to report. Nothing failed. Nothing ran.
That is the real risk with scheduled work. A crashed job at least leaves a stack trace somewhere. A job that quietly stopped running leaves an absence, and absences do not trigger alerts on their own. Teams usually discover these weeks later, when someone asks why the nightly backups have a gap, or why an invoice batch covers a suspiciously short period.
How does cron job monitoring work?
Cron job monitoring uses a heartbeat, sometimes called a dead man's switch. You create a monitor with an expected schedule and a unique URL. At the end of your job, after the work succeeds, the script pings that URL. The monitoring service records the ping and starts a timer. If the next ping does not arrive within the grace period you set, the monitor decides the job failed and alerts you. The logic is inverted from normal monitoring: silence is the alarm, not the all-clear.
Adding it to an existing job is usually one line. A shell job becomes:
0 3 * * * /usr/local/bin/nightly-backup.sh && curl -fsS https://your-monitor-url
The && matters. It means the ping only fires if the script exited successfully, so a job that runs and fails is treated the same as a job that never ran. Both produce no ping, and both alert.
What is a dead man's switch in monitoring?
A dead man's switch is a check that fires an alert when an expected signal stops arriving, rather than when an error is reported. In monitoring terms, your job proves it is alive on every run, and the monitor raises an incident the moment the proof stops coming. It is the only pattern that reliably catches a job which never executed, because it does not depend on the job, the server, or the scheduler being healthy enough to report its own failure. The name comes from the rail industry, where a driver had to keep pressure on a pedal to keep the train moving.
What should you monitor with a heartbeat check
Anything that runs on a schedule and matters if it stops. In practice that means more than the classic nightly cron:
| Job type | What goes wrong when it stops | Suggested grace period |
|---|---|---|
| Database backups | An unrecoverable gap you find during a restore | 2 to 4 hours |
| Billing or invoice batches | Revenue not collected, customers not charged | 1 hour |
| ETL and data syncs | Stale dashboards and wrong numbers in reports | 30 to 60 minutes |
| Email digests and notifications | Silent drop in engagement nobody attributes to a bug | 1 hour |
| Certificate renewal jobs | An expired certificate takes the site offline | 1 day |
| Cache warming and sitemap generation | Slow pages, stale search index | 2 hours |
| Queue workers and consumers | Work piles up unprocessed with no visible error | 15 to 30 minutes |
Set the grace period from the job's real variance, not its ideal runtime. A backup that normally takes 20 minutes but occasionally takes 90 under load needs a window that tolerates 90, or you will train your team to ignore the alerts.
How do you monitor a cron job that runs on a schedule you cannot change?
Wrap it instead of editing it. If the job is managed by another team, or lives inside an application scheduler you do not control, you can still ping from the code path that does the work: at the end of the function, after the commit, send the heartbeat. Laravel's scheduler, systemd timers, Kubernetes CronJobs, GitHub Actions schedules, and Windows Task Scheduler can all issue an HTTP request as a final step. What matters is that the ping happens after success, not before the work starts, otherwise you are monitoring that the job launched rather than that it finished.
Cron monitoring versus uptime monitoring
They answer different questions and you generally want both. Uptime monitoring asks the outside world whether your service responds right now; the monitor initiates the check. Cron monitoring waits for your job to check in; your system initiates the signal. A site can be perfectly up while every scheduled job behind it has been dead for a week, and the reverse happens too, where jobs run fine on a server whose web tier is down. Running cron and heartbeat checks alongside uptime checks covers both halves: the request path your customers use, and the background work they depend on without knowing it.
What to alert on beyond a missed run
A missed ping is the headline, but two other signals are worth wiring up once the basics work. The first is duration. If you ping at the start and again at the end, the monitor can measure runtime and flag a job that finished but took four times longer than usual, which is often the early warning before it eventually times out entirely. The second is exit status. Sending a failure signal on a non-zero exit gives you an immediate alert with the error, rather than waiting out the grace period. For a job in an ETL chain, a late finish matters as much as a missed one, since every downstream table stays stale until it lands, which is why teams running data pipelines usually pair job alerts with something that tracks which downstream tables went stale when an upstream job slipped.
How to set up cron job monitoring
The whole setup takes a few minutes per job.
- List every scheduled job you have. Check crontabs on each server, your application scheduler, systemd timers, and any CI schedules. Most teams find jobs nobody remembered.
- Rank them by what breaks if they stop. Backups, billing, and anything customers see go first. You do not need to monitor a cache-clearing job on day one.
- Create a heartbeat monitor per job with the expected schedule and a grace period based on real observed runtime, not the best case.
- Add the ping to the end of the job, guarded so it only fires on success. Use
&&in shell, or place the request after the last successful step in code. - Deliberately break one job and confirm the alert lands in the channel your team actually watches. An untested alert path is not monitoring, it is a decoration.
- Route alerts by severity. A missed backup should reach a person. A missed sitemap regeneration can go to a channel. Alert routing is what decides whether either one gets seen.
Do you need a paid tool for this?
You can build a rough version yourself with a table of last-seen timestamps and a job that checks it, but that solution has the same weakness as a self-hosted monitor: it runs on the infrastructure it is watching. If the server dies, the watcher dies with it and nothing alerts. An external service is checking from outside your failure domain, which is the entire point. The practical answer for most teams is to use a monitor that handles both, so heartbeat checks for your jobs sit in the same dashboard and the same alert routing as the uptime checks for your sites and APIs, instead of paying for and configuring two separate products. Uptimehub counts a heartbeat as an ordinary monitor on a flat per-monitor plan, so watching your jobs does not land on a separate bill.
The short version
Cron will not tell you when a job stops running, because from its point of view nothing happened. Heartbeat monitoring inverts that: your job proves it is alive on each successful run, and silence past the grace period raises the alert. Start with backups and billing, set grace periods from real runtimes, ping only on success, and test that the alert reaches someone. If you already run uptime monitoring for your sites, adding heartbeat checks for the jobs behind them is a small amount of work that closes the gap most teams do not know they have. For a wider view of what to watch and how often, see our guide to choosing check intervals.
Know your site is down before your customers do
Start monitoring your sites, APIs and services from six regions, with alerts by Slack, email, SMS and webhook and a branded status page. Transparent, flat pricing per monitor.