What it means

The cron daemon (cron on Debian and Ubuntu, crond from cronie on RHEL and Fedora) reads crontabs once a minute and starts every entry whose time matches. It never reports a job that did not start, and it reports a failing job only by mail. So "not running" covers two cases, and the log separates them:

grep CRON /var/log/syslog | tail -n 20      # Debian, Ubuntu
sudo journalctl -u cron --since today       # crond on RHEL, or /var/log/cron
CRON[48211]: (deploy) CMD (/usr/local/bin/backup.sh)

A CMD line means cron started the command. No line at the expected minute means it never did.

Common causes

1. The command fails in cron's environment. The job ran (there is a CMD line) but cron uses /bin/sh, PATH=/usr/bin:/bin and your home directory as the working directory. A command that works in your terminal is not found, a relative path misses its file, a variable from .bashrc is empty.

2. The output, and the error with it, is discarded. Without a mail server cron logs No MTA installed and drops whatever the job printed.

3. The daemon is not running. Common in Docker images, which start only your app:

systemctl status cron        # or crond
pgrep -a cron

4. The entry is not where you think. crontab -e as another user, a crontab lost when the server was rebuilt, or a syntax error that refused the install. List what is installed:

crontab -l                  # current user
sudo crontab -l -u deploy   # another user
ls /etc/cron.d

5. Cron ignores the file. On Debian and Ubuntu, files in /etc/cron.d whose name contains a dot (backup.cron) are skipped. Files there and in /etc/crontab need a user field after the five time fields, must be owned by root, and must end with a newline.

6. An unescaped %. In a crontab line, % becomes a newline. date +%F cuts the command short: write date +\%F.

7. The wrong timezone. Cron uses the server's timezone (timedatectl), not yours.

How to fix

  1. Check the log for a CMD line at the expected minute.
  2. No line: start the daemon, check crontab -l for the right user, rename files in /etc/cron.d without a dot, and add the user field and a final newline.
  3. A line but no result: send the output to a file to read the error, >> /var/log/backup.log 2>&1.
  4. Use absolute paths for every command and file, or set PATH=/usr/local/bin:/usr/bin:/bin at the top of the crontab.
  5. Reproduce cron's environment to test the command: env -i HOME="$HOME" PATH=/usr/bin:/bin /bin/sh -c '/usr/local/bin/backup.sh'.

Is it just you?

If the schedule itself is in doubt, paste it into the cron expression generator: it explains each field and lists the next run times in your timezone.

Get alerted next time

  • A healthcheck expects a ping from the job on its own cron expression and timezone, and alerts when it does not arrive within the grace period.
  • Chain the ping with && so a run that exits with an error sends nothing and alerts like a missed run, or call the URL with /fail appended to alert at once.
  • Ping /start when the job begins and the plain URL at the end: each run's duration is logged, and a run that hangs misses its deadline.