Cron Job Monitoring With Heartbeats (crontab and systemd)
Heartbeat monitoring catches cron jobs that fail or stop running. Each job calls a unique URL when it finishes successfully, and the monitor alerts you if that call does not arrive within the expected interval. Append the ping with && so it is only sent when the job exits with status 0.
To monitor a cron job, create a heartbeat monitor, which gives you a unique URL, and make the job request that URL every time it finishes successfully. If the request does not arrive within the expected interval, the monitor alerts you. The key detail is to chain the ping with &&, so it only fires when the job exits with status 0:
/usr/local/bin/backup.sh && curl -fsS --retry 3 https://pingrobot.io/hb/<token> > /dev/null
This catches the failures cron itself never reports: jobs that crash, jobs that hang, and jobs that stop being scheduled at all.
Why scheduled jobs fail silently
Cron was designed to run commands, not to tell you whether they worked. In practice:
- Output goes nowhere. Cron mails output to the local user if a mail transfer agent is configured. On most modern servers and containers it is not, so errors vanish.
- Missing runs produce no output. If the crontab was lost during a migration, the cron daemon stopped, or the server was replaced, nothing runs and nothing complains.
- Partial success looks like success. A backup script can create an empty file and exit happily if one step is not checked.
- Environment differences. Cron runs with a minimal
PATHand no login shell, so a script that works in your terminal can fail under cron.
Active monitoring cannot see any of this because there is no URL to check. A heartbeat flips the direction: the job reports in, and the absence of a report is the alert.
How heartbeat monitoring works
- You create a heartbeat monitor and set the expected interval (how often the job runs) and a grace period (how late it may be).
- The monitor gives you a unique, secret URL.
- Your job requests that URL at the end of each successful run.
- If no request arrives within interval plus grace, the monitor marks the job as down and alerts you.
- When the next ping arrives, it marks the job as up again.
Treat the token in the URL like a password. Anyone who has it can send fake "success" pings.
The curl command, explained
curl -fsS --retry 3 https://pingrobot.io/hb/<token>
| Flag | Meaning |
|---|---|
-f |
Return a non-zero exit code on HTTP errors instead of printing the error page |
-s |
Silent: no progress bar in your logs |
-S |
Still show an error message if the request fails (used with -s) |
--retry 3 |
Retry up to three times on transient errors such as timeouts |
You can add --max-time 10 so a stuck network connection never holds up the job's shell for long. Redirect output with > /dev/null if you do not want the response body in cron mail or logs.
Why && and not ;
job && curl ...pings only if the job exited with status 0. A failed job means no ping, and the monitor alerts you.job ; curl ...pings no matter what. The monitor would only notice when the job stops being scheduled, not when it fails.
This only works if your script exits non-zero on failure. In shell scripts, add set -eu near the top so that an unhandled error or an undefined variable stops the script with a failing exit code. If your script uses pipelines, set -o pipefail is supported in bash and in recent versions of many other shells.
crontab examples
Edit the crontab with crontab -e.
Nightly backup at 02:30
30 2 * * * /usr/local/bin/backup.sh && curl -fsS --retry 3 https://pingrobot.io/hb/<token> > /dev/null
Every 15 minutes, with logging
*/15 * * * * /usr/local/bin/sync-orders.sh >> /var/log/sync-orders.log 2>&1 && curl -fsS --retry 3 https://pingrobot.io/hb/<token> > /dev/null
The && still applies to the script's exit code, because redirection does not change it.
A command with a date in it
In crontab, % is a special character that starts standard input, so it must be escaped:
0 3 * * * pg_dump mydb > /backups/mydb-$(date +\%F).sql && curl -fsS --retry 3 https://pingrobot.io/hb/<token> > /dev/null
Prevent overlapping runs
If a job can run longer than its interval, wrap it with flock so two copies never run at once:
*/5 * * * * flock -n /tmp/import.lock /usr/local/bin/import.sh && curl -fsS --retry 3 https://pingrobot.io/hb/<token> > /dev/null
With -n, flock exits with an error if the lock is held, so a skipped run does not ping. If the previous run hangs indefinitely, the pings stop and you get alerted.
systemd timer example
On systems that use systemd, timers are an alternative to cron with better logging through journalctl.
/etc/systemd/system/backup.service
[Unit]
Description=Nightly backup
[Service]
Type=oneshot
ExecStart=/bin/sh -c '/usr/local/bin/backup.sh && curl -fsS --retry 3 https://pingrobot.io/hb/<token> > /dev/null'
/etc/systemd/system/backup.timer
[Unit]
Description=Run nightly backup
[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true
[Install]
WantedBy=timers.target
Enable and check it:
sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
systemctl list-timers backup.timer
journalctl -u backup.service --since today
Notes:
Persistent=trueruns a missed job after boot if the machine was off at the scheduled time.- In unit files,
%is a specifier character. If your command contains one, write it as%%. - Instead of the
sh -cwrapper, you can put the curl call inExecStartPost=. systemd runsExecStartPost=only afterExecStart=succeeded, so the ping still depends on the job's result.
Other places heartbeats fit
The same pattern works anywhere you can run a command or make an HTTP request:
- Kubernetes CronJobs: add the curl call after the main command in the container's shell command.
- Windows Task Scheduler: run a PowerShell script that ends with
Invoke-WebRequest -UseBasicParsing https://pingrobot.io/hb/<token>after the work succeeds. - Application code: call the URL at the end of a queue worker loop or a scheduled task in your framework, so you learn when the worker stops processing.
- Backups on a NAS or router: many devices can run a post-job script.
Choosing interval and grace
| Job | Schedule | Suggested period | Suggested grace |
|---|---|---|---|
| Queue worker heartbeat | Every minute | 1 minute | 5 minutes |
| Order sync | Every 15 minutes | 15 minutes | 10 minutes |
| Hourly report | Hourly | 1 hour | 15 minutes |
| Nightly backup | Daily | 1 day | 1 to 2 hours |
| Weekly cleanup | Weekly | 7 days | 6 hours |
The grace period should cover the longest normal run time plus a margin for clock drift and retries. Too short, and slow but healthy runs page you. Too long, and you learn about a broken backup a day late.
Checklist
- One heartbeat monitor per job, named after the job and the server
- Ping chained with
&&, never; - Script uses
set -euand exits non-zero on failure %escaped in crontab and in systemd units- Token stored like a secret, not committed to a public repository
- Test once: run the job manually, confirm the ping arrives; then break it on purpose and confirm the alert
- Alerts sent to a channel someone watches
PingRobot supports heartbeat monitors alongside HTTP(S), keyword and TCP port checks, so cron jobs and websites alert through the same channels. For the bigger picture, see what is uptime monitoring.
Start monitoring free — 10 monitors, no credit card.
Frequently asked questions
Why not just check the cron job's log file?
A log only helps if someone reads it, and it says nothing when the job never ran at all, for example after a server rebuild, a deleted crontab or a stopped cron daemon. A heartbeat alerts on silence.
What grace period should I use for a heartbeat?
Use the job's normal schedule plus its longest expected run time plus a margin. A nightly backup that usually takes 20 minutes but sometimes 45 could use a 24-hour period with a 1-hour grace.
Does the heartbeat ping slow down or break my job?
No. It is one HTTPS request after the job finishes. With curl's retry and timeout options, a temporary network problem delays it by a few seconds at most and does not change the job's own result.
Should I ping on failure too?
Pinging only on success is the simplest reliable pattern, because any failure, crash or missed run produces the same signal, which is silence. That is enough to alert you.