Skip to content
ohsudo, Home
Linux

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.

Beginner4 min readApplies to cron (Debian/Ubuntu) · cronie (RHEL / Rocky / AlmaLinux)

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?

  1. Your shellyour PATH, bash, .bashrc(works)
  2. cronminimal PATH, /bin/sh, no .bashrc
  3. The job fails silentlyits output goes nowhere(breaks here)
The same script, two environments. Cron starts it with a short PATH, /bin/sh, none of your shell's settings, and throws its output away, so a failure tells nobody.

TL;DR

Terminal window
grep CRON /var/log/syslog | tail # Debian/Ubuntu: did it start?
sudo tail /var/log/cron # RHEL family
crontab -l # is the entry really there?

And make the job keep its output:

*/5 * * * * /opt/scripts/backup.sh >> /var/log/backup-cron.log 2>&1

1. Did cron start the job?

Terminal window
grep CRON /var/log/syslog | tail -n 20 # Debian / Ubuntu
sudo journalctl -u cron -n 20 # Debian / Ubuntu, with systemd
sudo 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, or crond on 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:

/etc/cron.d/backup
# crontab -e
30 3 * * * /opt/scripts/backup.sh
30 3 * * * root /opt/scripts/backup.sh

Files 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>&1

Without 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:/bin

None 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.

Terminal window
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.

Tools

Try it yourself

Articles

Related articles