How do I get alerted when a cron job silently stops running?
A cron job that stops running produces no error, so there is nothing for an error tracker or log search to catch. Use a dead-man's switch: have the job ping a monitor URL every time it finishes, and let the monitor alert you when an expected ping does not arrive within a grace window. Also send an explicit failure ping when the job runs but fails.
Why it happens
Most monitoring waits for something bad to happen: an exception, a 500, an error line in a log. A scheduled job can fail in a way that produces none of those. The cron daemon was stopped in a deploy, the container that held the crontab was replaced, the server was rebuilt without the crontab, a schedule was edited and no longer matches, the job was paused during an incident and never resumed, or an earlier run is still holding a lock so every new run exits immediately. In each case nothing runs, so nothing can throw.
That is why the working pattern inverts the question. Instead of asking whether the job reported an error, you ask whether it reported at all. This is a dead man's switch: the job sends a small signal (an HTTP request to a unique URL, called a ping or check-in) when it finishes, and a separate service that knows the schedule raises an alert as soon as an expected signal does not arrive on time. The monitor has to live outside the machine that runs the job, otherwise it dies with it.
A missing signal covers jobs that never ran. It does not cover jobs that ran and failed quietly, such as a script that catches its own exception and exits with status 0, or a backup that wrote a zero-byte file. For those, the job has to say so itself: report failure explicitly, and send the success signal only after the work is actually verified.
How to get alerted when a scheduled job stops (works with any heartbeat or cron-monitoring tool)
Create one monitor per job and put its schedule in it
Each job gets its own monitor with a unique ping URL and the schedule you expect: a crontab expression or a plain interval. A monitor that knows the schedule can tell the difference between a job that is late and one that is simply not due yet. Name it after what the job does, and write in its description what to do when it fires, so whoever gets paged at night does not have to guess.
Send the success ping only after the work succeeded
Make the ping the last line of the job, and make it conditional on success. If it runs unconditionally, a job that crashes halfway still looks healthy. Use a retry on the HTTP call, because a dropped network packet should not look like a dead job, and keep the ping from failing the job itself if the monitor is unreachable.
#!/bin/sh set -e /usr/local/bin/nightly-backup curl -fsS -m 10 --retry 3 https://hc-ping.com/your-uuid-here > /dev/nullReport failure explicitly, and add a start ping for duration
Send a start signal when the job begins and a finish signal at the end. The monitor can then show how long each run took, and it can alert when a run starts but never finishes, which is a hung job rather than a missing one. When the job catches an error, send a failure signal right away instead of waiting for the grace window to expire. Healthchecks.io, for example, documents appending /start and /fail to the ping URL and treating a start without a matching success within the grace time as a failure (Healthchecks.io docs, read 2026-11-13).
curl -fsS -m 10 --retry 3 https://hc-ping.com/your-uuid-here/start if /usr/local/bin/nightly-backup; then curl -fsS -m 10 --retry 3 https://hc-ping.com/your-uuid-here else curl -fsS -m 10 --retry 3 https://hc-ping.com/your-uuid-here/fail fiSet a grace window that matches how the job actually behaves
A grace window is the extra time the monitor waits past the expected moment before it alerts. Too short and a slow day pages you for nothing; too long and a real outage is reported hours late. Look at the longest normal run in the last month, add a margin, and keep the window shorter than the interval so two missed runs do not look like one. If the job takes minutes, a few minutes of grace is usually enough; for a nightly job that can wait for a queue, give it more.
Handle timezone and daylight saving explicitly
Cron runs in the machine's local time unless you say otherwise. If the monitor assumes UTC and your server is on a local zone, every alert is shifted by hours. Tell the monitor the server's timezone, or run the host on UTC. Around daylight saving changes a job scheduled in the skipped hour does not run at all, and one in the repeated hour may run twice, so schedule important jobs outside the 01:00 to 03:00 local window or in UTC.
Send the alert where someone will see it, then test it by breaking the job
Route missed-run alerts to a channel that a person owns, not to a shared inbox. Then prove the setup works: disable the job (comment out the crontab line or stop the scheduler) and wait for one schedule period plus the grace window. If no alert arrives, the monitor is wrong, the destination is wrong, or the ping is being sent even when the job does not run. Re-enable the job and confirm the monitor returns to healthy.
| What went wrong | Error tracking alone | Success ping only | Start, success and failure pings |
|---|---|---|---|
| Scheduler stopped or crontab lost | Nothing | Alert after the grace window | Alert after the grace window |
| Job crashed with an exception | Alert if the error is captured | Alert after the grace window | Immediate failure alert |
| Job started and hangs forever | Nothing | Alert, but only after the next expected run is missed | Alert when the run exceeds its maximum duration |
| Job finished but did nothing useful | Nothing | Looks healthy | Looks healthy unless the script checks its own result |
| Job runs, slower every week | Nothing | Looks healthy | Visible as a trend in run duration |
Behaviour of start, success and failure signals and grace time follows the Healthchecks.io documentation (docs/ and docs/signaling_failures/, read 2026-11-13); the comparison columns are the reasoning from that model.
When to use Bugsly
Bugsly has cron monitoring built on the same check-in model. Wrap the job with the Sentry SDK helper (Sentry.withMonitor in JavaScript, the monitor decorator or capture_checkin in Python), or call the REST endpoint POST /api/v1/monitors/<slug>/checkins with a status of ok or error and an optional duration_ms. A monitor takes a crontab or interval schedule, a check-in margin (the grace window) and a timezone. It alerts when a run reports error and when an expected check-in is missed.
Both alert conditions, cron monitor missed and cron monitor error, can go to email, Slack, Telegram or a webhook. Cron monitoring needs a plan that includes monitors; see /pricing. An assistant connected over MCP can read the state with list_monitors and get_monitor, which returns recent check-ins, so you can ask which jobs are late without opening the dashboard.
Bugsly does not guess a schedule for you: the monitor needs a crontab or interval, and a check-in margin shorter than the interval is what keeps one missed run from hiding the next. Bugsly does not verify that the job did useful work either; it only knows what the check-in says, so report error yourself when a result looks wrong.
When Bugsly is not the right answer
If you need an on-call escalation product, with schedules, acknowledgements and a second person paged when the first does not answer, Bugsly is not it: its destinations are email, Slack, Telegram and webhooks, and it has no PagerDuty action. Keep the heartbeat monitor, but route its alert into a tool built for escalation.
If you only have one or two jobs and no other need for Bugsly, a dedicated open heartbeat service or a self-hosted checker is a perfectly good answer; the pattern in the steps above is the same regardless of vendor.
Ask your AI assistant to do it
With the Bugsly MCP server connected (https://bugsly.dev/api/mcp; setup for ChatGPT, Claude, Cursor and Gemini CLI is on /mcp), paste this. The assistant reads first and reports back; you decide what happens next.
Assign to Bugsly: use list_monitors for my project to list every cron monitor and its schedule, then get_monitor with checkin_limit 10 on each one and tell me which jobs have a missed or error check-in in the last 24 hours, and which have no check-ins at all. Do not change any monitor; show me what you found so I can approve the next step.
Frequently asked questions
How long should the grace window be?
Longer than your slowest normal run and shorter than the interval between runs. Check the last month of run durations, add a margin, and tighten it if alerts arrive too late for the job to matter.
Why not just alert on errors in the job's logs?
Because a job that never starts writes no log lines. Log-based alerts depend on the job running; a heartbeat depends on it not running, which is the case you are trying to catch.
Can the monitor itself fail silently?
Yes, if it runs on the same machine or the same scheduler as the job, so host it elsewhere. Also check that your alert destination works by disabling a job on purpose once in a while.
Do I need a start ping as well as a finish ping?
Not to detect a job that never runs. A start ping adds run duration and lets the monitor flag a job that began and never finished, which a finish-only check can only notice at the next scheduled time.
Sources
- Healthchecks.io documentation: introduction (dead man's switch, grace time, ping URLs) (read 2026-11-13)
- Healthchecks.io documentation: configuring checks (schedules, server timezone) (read 2026-11-13)
- Healthchecks.io documentation: signaling failures (read 2026-11-13)
- Sentry docs: Crons for Node.js (withMonitor, check-in margin, timezone) (read 2026-11-13)
- Sentry docs: Crons for Python (monitor decorator, capture_checkin) (read 2026-11-13)
Related
- Cron monitoring guide
- How do I find out my site is down before my customers tell me?
- Why does my team ignore error alerts?
- All problems and solutions
Sentry is a trademark of its owner; Bugsly is not affiliated with or endorsed by Sentry. “Sentry-compatible” only means Bugsly accepts events sent by Sentry's open SDKs.