
A note from the founder. Tired of finding out a cron job broke days after it happened? I'm looking for a small group of early users to try Runhooks and share honest feedback. Early adopters get upgraded plans for free.
Here's the uncomfortable truth about cron: it will run your job, and it will never tell you when it stops. No error. No email. No dashboard turning red. The report just doesn't get sent, the backup just doesn't happen, and you find out days later when someone asks where the data went.
Cron was built in 1975 to start processes. It was never built to tell you whether they worked. Monitoring is the layer you have to add yourself — and this guide covers exactly how to do it, from a five-minute heartbeat to full execution tracking.
Why Cron Jobs Fail Silently
The core problem is that cron's job is finished the moment it launches your command. Whatever happens after — a crash, a timeout, a non-zero exit code, a network error — is invisible to cron. There is no built-in concept of "success" or "failure," so there is nothing to alert on.
The most common ways a cron job dies without a sound:
- The daemon stopped. A server reboot without
cronre-enabled, or a killed container, and every job silently stops firing. - Wrong environment. Cron runs with a minimal
PATHand no shell profile, so a script that works in your terminal fails to find its interpreter or variables. - Expired credentials. An API token or database password rotates, the job starts returning
401, and it keeps "running" on schedule while accomplishing nothing. - A hung run. The job starts but blocks forever on a network call, so it never finishes and never errors.
- A bad exit code. The script fails, exits
1, and — because nothing is watching the exit code — the failure evaporates.
We cover the full taxonomy in Why Cron Jobs Fail in Production. The point here is that none of these produce an alert on their own. Detection is a separate system you have to build.
The DIY Baseline: Logs and MAILTO
The classic first step is to capture output and email it:
# Redirect stdout and stderr to a log file
0 * * * * /usr/local/bin/sync.sh >> /var/log/sync.log 2>&1
# Or email any output using cron's MAILTO
MAILTO="[email protected]"
0 * * * * /usr/local/bin/sync.sh
This is better than nothing, but it only catches jobs that run and produce output. It tells you nothing when:
- The whole cron daemon is down (no job runs, so no email is ever sent).
- The job hangs (it never finishes, so it never emails).
MAILTOsilently fails because there's no mail transfer agent configured (extremely common on cloud VMs and containers).
Log-and-email answers "did this run produce an error?" It cannot answer the question that actually matters: "did this job run at all?" For that, you need a monitor that alerts on absence.
Heartbeat Monitoring (the Dead Man's Switch)
A heartbeat flips the logic around. Instead of your job speaking up when it fails, it checks in when it succeeds — and the monitor alerts you when a check-in is missing. This is the dead man's switch pattern, and it's the single most effective upgrade you can make.
The mechanism is simple: after your job finishes successfully, it sends an HTTP request ("ping") to a monitoring URL. The monitor knows your job should ping every hour (or whatever your schedule is). If a ping doesn't arrive on time, it fires an alert.
# Ping a monitoring URL only if the job succeeds
0 * * * * /usr/local/bin/sync.sh && curl -fsS -m 10 https://your-monitor/ping/<uuid>
Because the curl runs only on a successful exit (&&), a crashed script produces no ping — and a missing ping is exactly what the monitor alerts on. Crucially, this also catches the failure that log-and-email can't: if the entire server goes down, the pings simply stop, and you get alerted.
For long-running jobs, ping at the start and the end so you can also detect a job that started but never finished:
JOB=https://your-monitor/ping/<uuid>
0 * * * * curl -fsS -m 10 $JOB/start && /usr/local/bin/sync.sh && curl -fsS -m 10 $JOB
Heartbeat monitoring is what tools like Healthchecks.io specialize in, and it's genuinely great at answering "did the job run?" Its limitation is that it's passive — it only knows what your ping tells it. It can't see the HTTP status your job returned, how long it took, or what the response body said. For that, you need the job's execution itself to be monitored.
Execution Monitoring: Watch the Run, Not Just the Result
Execution monitoring inverts the responsibility. Instead of your server running the job and hoping something notices failures, an external scheduler triggers the job over HTTP and watches the entire execution:
- It records the HTTP status code — so a job returning
500or401is flagged instantly, not silently retried into the void. - It captures the response body and duration — so you can see what the endpoint returned and whether it's slowing down.
- It knows the run should have happened — so if the scheduler itself can't reach your endpoint, that's an alert too.
- It can retry automatically — a transient timeout gets a second attempt in seconds, instead of waiting for tomorrow's tick.
This works because most scheduled work today is really an HTTP call: hit /api/sync, trigger a Supabase Edge Function, call a webhook. When the scheduler owns the request, monitoring stops being something you bolt on and becomes a property of every run. This is the model Runhooks is built on, and it's the difference between "a job didn't check in" and "the job returned 429 Too Many Requests at 03:00 after 2 retries and 14s of latency."
Choosing an Approach
| Approach | Catches missing runs | Catches failed runs | Sees why it failed | Setup effort |
|---|---|---|---|---|
| Logs + MAILTO | ❌ | Partially | Only from logs | Low |
| Heartbeat / dead man's switch | ✅ | ✅ | ❌ | Low |
| Execution monitoring | ✅ | ✅ | ✅ | Low |
- Just want to know a job stopped? Add a heartbeat. It's minutes of work and catches the scariest failure mode.
- Want to know why it failed, with logs and retries? Move the trigger to an execution-aware scheduler.
- Comparing specific vendors? See our breakdown of cron job monitoring tools, from DIY to fully managed.
How Runhooks Monitors Your Jobs
Runhooks schedules your HTTP jobs and monitors every execution — the two aren't separate products you have to wire together. When you create a job, you get:
- Execution logs — every run recorded with HTTP status, response body, and duration in milliseconds.
- Automatic retries — configurable attempts with exponential backoff, so a transient blip doesn't become a missed run.
- Failure alerts — email or webhook notifications the moment a job starts failing, not days later.
- Missed-run detection — if a scheduled execution can't reach your endpoint at all, that's an alert too.
There's nothing to install on your server and no heartbeat code to add — because Runhooks is the one making the request, it already knows exactly what happened.
Get Started
Cron will happily run a broken job forever. Monitoring is the layer that turns "we lost three days of data" into "we got paged at 3:01 AM." Whichever approach you choose:
- Add a heartbeat to your most important jobs today — it takes five minutes.
- For jobs where you need to know why they failed, try Runhooks and let the scheduler monitor each run for you.
- Preview and sanity-check your schedules with the cron expression visualizer.
Frequently Asked Questions
How do I know if a cron job failed?
By default, you don't — standard cron has no failure detection or alerting. A job that exits with an error, hangs, or never starts leaves no notification behind. To know, you need external monitoring: either a heartbeat (the job pings a URL after each successful run, and you're alerted if the ping is missing) or an execution-aware scheduler like Runhooks that fires the job, inspects the response, and alerts on any failure.
What is a dead man's switch for cron jobs?
A dead man's switch is a monitor that expects a regular signal and alerts you when the signal stops. For cron, your job sends an HTTP ping every time it finishes successfully. If the monitor doesn't receive the ping within the expected window, it assumes the job failed or never ran and notifies you. It catches the failure mode plain logging misses: a job that silently stops firing entirely.
What is the difference between heartbeat and execution monitoring?
Heartbeat monitoring is passive — your job reports in when it completes, and the monitor alerts on a missing check-in. It confirms a job ran but can't see why it failed. Execution monitoring is active — the scheduler triggers the job over HTTP, then records the status code, response body, duration, and retries. Heartbeat tells you a job didn't run; execution monitoring tells you what happened and why.
Can I monitor cron jobs for free?
Yes. Healthchecks.io offers a free heartbeat plan, and Runhooks includes a free plan that both schedules and monitors HTTP jobs with execution logs, retries, and failure alerts. The right choice depends on whether you only need to know a job stopped (heartbeat) or you want full execution history and automatic retries (execution monitoring).
Do I still need cron monitoring if I have uptime monitoring?
Yes. Uptime monitoring confirms your server answers HTTP requests. Cron monitoring confirms scheduled tasks actually execute and succeed. A server can report 100% uptime while every cron job on it silently fails — expired credentials, a full disk, or a stopped cron daemon. They are different failure modes and need separate monitoring.
Read next: Cron Job Monitoring Tools Compared · Why Cron Jobs Fail in Production · Best External Cron Job Services Compared