pingrobot

Blog / Uptime monitoring

Cron Job Monitoring With Heartbeats (crontab and systemd)

· 5 min read

Short answer

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 PATH and 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

  1. You create a heartbeat monitor and set the expected interval (how often the job runs) and a grace period (how late it may be).
  2. The monitor gives you a unique, secret URL.
  3. Your job requests that URL at the end of each successful run.
  4. If no request arrives within interval plus grace, the monitor marks the job as down and alerts you.
  5. 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=true runs 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 -c wrapper, you can put the curl call in ExecStartPost=. systemd runs ExecStartPost= only after ExecStart= 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 -eu and 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.