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/cronCRON[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 cron4. 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.d5. 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
- Check the log for a
CMDline at the expected minute. - No line: start the daemon, check
crontab -lfor the right user, rename files in/etc/cron.dwithout a dot, and add the user field and a final newline. - A line but no result: send the output to a file to read the error,
>> /var/log/backup.log 2>&1. - Use absolute paths for every command and file, or set
PATH=/usr/local/bin:/usr/bin:/binat the top of the crontab. - 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/failappended to alert at once. - Ping
/startwhen the job begins and the plain URL at the end: each run's duration is logged, and a run that hangs misses its deadline.
FAQ
How do I check if a cron job ran? ▼
Look for its CMD line in the cron log: grep CRON /var/log/syslog on Debian and Ubuntu, /var/log/cron on RHEL, or journalctl -u cron (crond on RHEL). A CMD line means cron started the command. Whether the command succeeded is another question: redirect its output to a log file to see it.
Why does my script work in the terminal but not in cron? ▼
Cron runs it with /bin/sh, a PATH of /usr/bin:/bin, no profile and a different working directory. Commands from /usr/local/bin, nvm, pyenv or ~/.local/bin are not found, relative paths point elsewhere and variables from your shell are missing. Use absolute paths and set PATH at the top of the crontab.
Does cron run jobs missed while the server was off? ▼
No. Cron only starts jobs whose time matches while it is running. A job scheduled at 03:00 on a server rebooted from 02:55 to 03:05 is skipped. anacron or a systemd timer with Persistent=true catch up missed runs.