Skip to content

Monitor a crontab job

Cron doesn’t tell anyone when a job fails. The script errors, the server reboots, or someone edits the crontab, and the job quietly stops. A heartbeat check notices: the job reports to Pingstack after it succeeds, and Pingstack alerts you when that report doesn’t arrive on time.

By the end of this recipe you’ll have a heartbeat check that expects the job’s schedule, a private file holding its ping URL, and a wrapper script that reports each successful run.

1. Create a heartbeat check that matches the schedule

Section titled “1. Create a heartbeat check that matches the schedule”

Find the job’s line in crontab -l:

Terminal window
0 3 * * * /opt/jobs/backup.sh

Create a heartbeat check and set its schedule to Cron with the same expression (0 3 * * *). Cron runs jobs in the server’s local timezone unless your cron supports and sets CRON_TZ, so choose the server’s timezone in Pingstack. Run date on the server if you’re not sure. See schedules and timezones.

Set a grace period longer than the job’s usual runtime, plus a margin. Attach a notification group.

Copy the location’s ping URL from the check in Pingstack. Anyone who has it can report a run, so keep it out of the crontab, scripts in version control and shell history. Save it in a file only the job’s user can read:

Terminal window
install -m 600 /dev/null ~/.pingstack-backup
read -rs URL && printf 'PINGSTACK_PING_URL=%s\n' "$URL" > ~/.pingstack-backup

read -rs waits for you to paste the URL without echoing it, then press Enter.

Run the job through a small wrapper that pings only when the job succeeds:

/opt/jobs/backup-monitored.sh
#!/usr/bin/env bash
set -euo pipefail
source ~/.pingstack-backup
/opt/jobs/backup.sh
curl --fail --silent --show-error --max-time 10 --retry 3 \
"$PINGSTACK_PING_URL" > /dev/null

set -euo pipefail stops the script at the first failing command, including a failure early in a pipeline, so a failed job sends nothing. --retry 3 covers a brief network blip.

Make it executable and point the crontab at it:

Terminal window
chmod 700 /opt/jobs/backup-monitored.sh
Terminal window
0 3 * * * /opt/jobs/backup-monitored.sh

If the job can run longer than its schedule, stop runs from overlapping with flock:

Terminal window
0 3 * * * flock -n /tmp/backup.lock /opt/jobs/backup-monitored.sh

The pingstack command replaces the wrapper script, and also reports failures right away with the end of the job’s output, plus each run’s time:

Terminal window
0 2 * * * set -a; . "$HOME/.pingstack-backup"; /usr/local/bin/pingstack run -- /opt/jobs/backup.sh

Run the wrapper by hand as the cron user:

Terminal window
/opt/jobs/backup-monitored.sh

Open the check in Pingstack and confirm the location received a heartbeat and shows a next deadline. A run that works by hand but not from cron usually means cron’s environment is different: a shorter PATH, no shell profile, or a different working directory. Use absolute paths in the wrapper.

Send a count the job produces as a numeric value, then set warning and critical thresholds on the check. A backup that succeeds but is far too small can then still raise an alert.

Terminal window
SIZE_MB=$(du -sm /backups/latest | cut -f1)
curl --fail --silent --show-error --max-time 10 --retry 3 \
--get --data-urlencode "value=$SIZE_MB" "$PINGSTACK_PING_URL" > /dev/null
  • Timezone mismatch. The check expects 03:00 in its timezone; cron ran at 03:00 in the server’s. Make them the same, or use UTC on both.
  • Daylight saving. Local-time schedules shift twice a year. UTC avoids it.
  • Slow runs. A backup that grows over time eventually outlasts the grace period. Lengthen it.
  • Server asleep or down. Cron doesn’t catch up on runs missed while the machine was off. That’s an alert worth getting.

If alerts still don’t match what you expect, see troubleshooting monitoring.