Blog

Cron job monitoring: how to catch a job that silently stopped

Cron job monitoring for jobs that stop without an error: why it happens, four methods compared, and heartbeat setup with crontab and systemd lines.

By Sandeep · · 5 min read

You own scheduled jobs: backups, reports, syncs, certificate renewals. The worst failure is the one that makes no noise: the job stops running and nothing says so. This post explains why that happens, reviews four ways to monitor cron jobs and what each misses, and shows how to set up a heartbeat with copyable crontab and systemd lines.

What exactly is a cron job?

A cron job is a command that the cron daemon on a Unix-like system runs on a schedule. The schedule is a line in a crontab with five time fields (minute, hour, day of month, month, day of week) followed by the command.

30 2 * * * /usr/local/bin/backup.sh

This line runs the backup at 02:30 every day.

systemd timers, Kubernetes CronJobs and cloud schedulers do the same job, and the rest of this post applies to them too.

Is cron outdated?

Cron is not outdated. It dates from the 1970s and is still installed on almost every Linux server. Newer schedulers such as systemd timers, Kubernetes CronJobs and AWS EventBridge Scheduler add features. All of them share the same weakness: when a scheduled job does not run, nothing tells you.

Why cron jobs fail silently

Ordinary monitoring raises an alert when something bad happens. A job that did not run produces no event at all. There is no error, no log line, and no metric.

It happens in several ways:

People find out later: backups that stopped weeks ago, invoices that did not go out, certificates that did not renew, data syncs that went stale.

Four ways to monitor cron jobs

There are four common methods, and each misses something.

Method How it works What it misses
Email from cron (MAILTO) Cron emails any output of the job Needs working mail on the host; tells you about errors, not about a job that never ran; easy to ignore
Log or exit-code alerts The job logs a failure or a metric and the monitoring alerts on it A job that never started logs nothing
Check the result A separate check looks at the output, for example the age of the newest backup file Reliable, but you write one check per job
Heartbeat (dead man's switch) The job pings a URL when it succeeds; an alert fires when a ping is late The monitor must run somewhere other than the host it watches

The first two methods only catch failures that produce output. If the job never starts, email sends nothing and logs stay empty. Checking the result works, but you must write and maintain a separate check for every job.

Heartbeat monitoring covers the no-event case. Many teams use exit-code alerts and a heartbeat together: the exit code says why a job failed, and the heartbeat catches the job that never ran.

How heartbeat monitoring works

Heartbeat monitoring, also called a dead man's switch or check-in monitoring, turns the logic round: the job reports success, and silence is the alarm.

It catches every failure in the earlier list because all of them end with no ping. A job that never started, a script that failed, or a host that went down all produce silence.

The monitor must run somewhere other than the host it watches, so that a host that dies takes only the job with it and the late ping still raises an alert.

How to set up cron job monitoring with a heartbeat

  1. Create a check with an expected interval.

  2. Set the interval a little longer than the schedule so a slow run does not raise an alert. A daily job needs 25 hours rather than 24. A job every 5 minutes needs 7 minutes.

  3. Add the ping to the end of the job so it runs only on success.

    For crontab:

    # crontab: ping only when the job succeeds
    30 2 * * * /usr/local/bin/backup.sh && curl -fsS -m 10 --retry 3 'YOUR_PING_URL' > /dev/null
    

    For systemd:

    # systemd service
    ExecStart=/bin/sh -c '/usr/local/bin/backup.sh && curl -fsS -m 10 --retry 3 YOUR_PING_URL'
    

    What the curl flags do: -f fails on an HTTP error, -sS is quiet but still shows errors, -m 10 gives up after 10 seconds so the ping cannot hang the job, and --retry 3 retries a failed ping. With &&, a failed job skips the ping. For a Kubernetes CronJob, put the same && curl at the end of the container command.

  4. Decide who is notified when the ping is late.

  5. Test it: disable the job once and watch the alert arrive.

Cron job monitoring best practices

Cron job monitoring tools

Dedicated services include Healthchecks.io (open source, can be self-hosted), Cronitor, and the cron monitoring in Sentry and UptimeRobot. If you already run an on-call tool, a heartbeat there puts the missed job straight onto the same escalation policy as other alerts.

Heartbeats in OnCallAlerting

An OnCallAlerting heartbeat has a name, an interval from 1 minute to 30 days, an escalation policy, and a severity (Critical, Warning, or Info; Warning is the default). Creating it shows a ping URL once. Treat it like a password, and rotate it if you lose it.

The job sends a GET or POST to the ping URL. No headers or body are needed. Pings are limited to 60 per minute per heartbeat. A new heartbeat waits for its first ping and cannot go down before it.

When a ping is late, the heartbeat is marked Down. One incident opens on the escalation policy with the title "Heartbeat missed:" followed by the name. A heartbeat that stays down opens no further incidents. The next ping marks it Up and resolves the incident.

OnCallAlerting does not run the job, read its logs, or check its output. Notifications are Slack and in-app only. Setup is in the heartbeats docs and the escalation policies docs. To try it, start a 30-day trial.

Frequently asked questions

Why do cron jobs fail silently?

Cron jobs fail silently because they produce no event, error, or log line when they do not run. Common causes include missing crontabs on rebuilt servers, commented-out jobs, expired credentials, or hosts that were scaled away.

How do I monitor a cron job that never starts?

Use heartbeat monitoring where the job pings a URL only on success. If the ping is late, the monitor raises an alert, catching jobs that never started or hung without producing output.

What curl flags should I use for cron job heartbeats?

Use `-f` to fail on HTTP errors, `-sS` to stay quiet but show errors, `-m 10` to timeout after 10 seconds, and `--retry 3` to retry failed pings. The `&&` operator ensures the ping only runs if the job succeeds.

How long should the heartbeat interval be?

Set the interval slightly longer than the job schedule to tolerate slow runs. For a daily job, use 25 hours instead of 24, and for a job every 5 minutes, use 7 minutes.

What happens when a heartbeat is missed in OnCallAlerting?

The heartbeat is marked Down and one incident opens on the escalation policy with the title 'Heartbeat missed:' followed by the name. The next successful ping marks it Up and resolves the incident.