You add a line to crontab, wait for the minute to tick over, and nothing happens. No error, no output, no log line you can find. Cron fails quietly, which is why it feels haunted.
It almost always comes down to one of eight causes. Here they are in the order worth checking, each with a one-line test.
1. Cron isn't running
Check the daemon first. It takes five seconds.
systemctl status cron # Debian, Ubuntu systemctl status crond # RHEL, Rocky, Alma, Fedora If it's inactive, sudo systemctl enable --now cron. Minimal container images often don't ship cron at all.
2. It ran, but you can't see it
By default cron mails the output to the local user, and most servers have no mail set up, so the output disappears. Check whether cron even tried:
grep CRON /var/log/syslog # Debian, Ubuntu (if rsyslog is installed) journalctl -u cron --since today # anywhere with systemd Then make every job write its own log, so the next failure is visible:
*/5 * * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>&1 3. PATH is almost empty
Your shell has a long PATH. Cron's is usually just /usr/bin:/bin. So docker, node, pg_dump or anything in /usr/local/bin is "command not found", and you never see the error (see #2).
Fix it with absolute paths, or set PATH at the top of the crontab:
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin Find the real path with command -v docker.
4. It's not bash, and % is special
Cron runs commands with /bin/sh, which is dash on Debian and Ubuntu. Bash-only syntax like [[ ]], source or {1..5} breaks. Put the logic in a script with a #!/bin/bash shebang and call the script.
And % in a crontab line means "newline". This one fails silently:
0 2 * * * tar czf /backups/site-$(date +%F).tgz /var/www Escape it as \%, or move the command into a script.
5. Wrong file, wrong format
There are two crontab formats, and mixing them up is very common:
-
crontab -e(per user): 5 time fields, then the command. -
/etc/crontaband/etc/cron.d/*: 5 time fields, then a user name, then the command.
# /etc/cron.d/backup 0 2 * * * root /opt/scripts/backup.sh >> /var/log/backup.log 2>&1 Files in /etc/cron.d have extra rules on Debian and Ubuntu: the file name can only contain letters, digits, - and _ (so backup.cron is silently ignored), the file must end with a newline, and it must be owned by root and not writable by group or others.
6. Permissions and the script itself
- Is the script executable?
chmod +x /opt/scripts/backup.sh - Does it have a shebang on line 1?
- Windows line endings break the shebang:
file backup.shsays "with CRLF line terminators". Fix withsed -i 's/\r$//' backup.sh. - If
/etc/cron.allowexists, only the users listed in it can use cron.
7. Different time zone than you think
Cron uses the server's time zone, not yours. A job set for 0 9 * * * on a UTC server runs at 9:00 UTC.
timedatectl If you change the time zone, restart cron so it picks up the change.
8. It works by hand, fails in cron
That's an environment difference: cron has no HOME you expect, no .bashrc, no SSH agent, and a different working directory. Reproduce cron's environment instead of guessing:
env -i HOME=/root PATH=/usr/bin:/bin /bin/sh -c '/opt/scripts/backup.sh' If it fails there, you've found it. Use absolute paths for files too, and cd into the directory at the top of the script if it needs one.
Quick checklist
systemctl status cron-
journalctl -u cron: did it try? - Output redirected to a log file
- Absolute paths, or
PATH=at the top - Script with a shebang, no unescaped
% - User field in
/etc/cron.d, and no dot in the file name -
chmod +x, no CRLF -
timedatectlfor the time zone
More fixes
We cover problems like this every week on the VeloxaHost blog, including the Linux server hardening checklist.
Disclosure: we run VeloxaHost, a web hosting and VPS provider. Everything above works on any Linux server.