Cron job not running? Find the reason in the log, PATH and environment
Fix a cron job that does not run or fails silently: check the log, PATH and environment, % signs, /etc/cron.d rules, permissions and lost output.
by OhSudo ··
The script works when you run it by hand. In cron, nothing happens — no error, no output, no result. That’s the classic cron problem: cron gives a script a very different environment than your shell, and when something fails, it tells nobody.
Two questions find almost every cause: did cron start the job at all, and what did the job print when it ran?
- Your shellyour PATH, bash, .bashrc(works)works by hand; in cron
- cronminimal PATH, /bin/sh, no .bashrcruns it differently(fails)
- The job fails silentlyits output goes nowhere(breaks here)
TL;DR
grep CRON /var/log/syslog | tail # Debian/Ubuntu: did it start?sudo tail /var/log/cron # RHEL familycrontab -l # is the entry really there?And make the job keep its output:
*/5 * * * * /opt/scripts/backup.sh >> /var/log/backup-cron.log 2>&11. Did cron start the job?
grep CRON /var/log/syslog | tail -n 20 # Debian / Ubuntusudo journalctl -u cron -n 20 # Debian / Ubuntu, with systemdsudo tail -n 20 /var/log/cron # RHEL / Rocky / AlmaLinux (cronie)A started job leaves a line like this:
CRON[2481]: (guest) CMD (/opt/scripts/backup.sh >> /var/log/backup-cron.log 2>&1)- The line is there → cron did its part; the job itself fails. Go to section 3.
- No line at all → cron never started it. Check that cron runs (
systemctl status cron, orcrondon RHEL), then the schedule and the file (section 2).
2. Cron never starts it
The schedule isn’t what you think. 0 2 * * 1 is Monday at 02:00 — in the server’s time zone, which on many servers is UTC (timedatectl shows it). Paste the line into the Cron Builder to see the next run times in plain words.
One rule surprises everybody: if you set both the day of the month and the day of the week, cron runs when either matches. 0 9 1 * 1 runs on the 1st of every month and on every Monday.
The entry is in the wrong place. Your own crontab (crontab -e) has five time fields and the command. /etc/crontab and files in /etc/cron.d/ have a user field between them:
# crontab -e30 3 * * * /opt/scripts/backup.sh30 3 * * * root /opt/scripts/backup.shFiles in /etc/cron.d are ignored silently when:
- the file name has a dot in it (
backup.cron) — on Debian/Ubuntu only letters, digits,-and_count; - the file isn’t owned by root, or others can write to it;
- the last line doesn’t end with a newline — the last job is then simply skipped. Add an empty line at the end.
3. The job starts, then fails
Make it tell you why. Append its output to a log file, errors included:
*/5 * * * * /opt/scripts/backup.sh >> /var/log/backup-cron.log 2>&1Without that, cron tries to email the output to the user. Most servers have no mail system, so it’s thrown away — Debian/Ubuntu even logs it: (CRON) info (No MTA installed, discarding output).
Then look for these, in order:
The PATH is minimal. Cron runs jobs with PATH=/usr/bin:/bin. Anything in /usr/local/bin, /usr/sbin, /snap/bin or your home folder is “command not found”. Use full paths (/usr/local/bin/aws), or set PATH at the top of the crontab:
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binNone of your shell setup is loaded. No .bashrc, no .profile, no variables you exported, no conda or nvm, no SSH agent. If the script needs a variable, set it in the script or the crontab.
The shell is sh, not bash. Cron runs the command line with /bin/sh (dash on Debian/Ubuntu). Bash syntax like [[ … ]] or source fails there. Put #!/bin/bash in the script and call the script, not the commands inline — or add SHELL=/bin/bash to the crontab.
% means “new line” in a crontab. Everything after the first % becomes the command’s input. Escape it:
0 1 * * * tar czf /backup/site-$(date +\%F).tar.gz /var/www # \% , not %Relative paths. The job starts in the user’s home folder, not where the script lives. Use absolute paths, or cd to the right folder first.
Permissions. The script needs the execute bit (chmod +x), and the cron user must be able to read everything it touches — see Permission denied when running a script.
4. Reproduce cron’s environment
The fastest way to see what cron sees: run the script with an empty environment, the way cron would.
env -i HOME=$HOME SHELL=/bin/sh PATH=/usr/bin:/bin /bin/sh -c '/opt/scripts/backup.sh'If it fails here and works in your normal shell, the difference is the environment — usually PATH or a missing variable.
Note
systemd timers are an alternative for anything important: every run is in journalctl -u <name>, systemctl list-timers shows the next run, and a missed run (the server was off) can still happen at boot with Persistent=true.
Shell, we have a problem!
Similar, but different: crontab: command not found — cron isn’t installed (sudo apt install cron / sudo dnf install cronie). You (guest) are not allowed to use this program (crontab) — the user is blocked by /etc/cron.allow or /etc/cron.deny. A job that runs twice usually exists both in your crontab and in /etc/cron.d/.
Found this useful? Share it.
Found a mistake? Let us know at contact@ohsudo.com.