You added a line to your crontab, waited, and nothing happened. Or the script works perfectly when you run it in your terminal, but cron never seems to run it. This is one of the most common Linux problems, and it nearly always comes down to a short list of causes. Work through them in this order and you will find it.
1. Is the cron daemon actually running?
Start with the obvious. On Debian and Ubuntu the service is called cron; on RHEL, Fedora, Rocky and Alma it is crond.
systemctl status cron # Debian / Ubuntu
systemctl status crond # RHEL family
If it is inactive, start and enable it:
sudo systemctl enable --now cron
Inside Docker containers there is usually no cron daemon at all unless you install and start one yourself. A crontab file in an image does nothing on its own.
2. Did cron try to run the job?
Cron logs every job it starts. Check the log before you change anything else, because it tells you whether the problem is the schedule or the command.
grep CRON /var/log/syslog # Debian / Ubuntu
sudo tail -f /var/log/cron # RHEL family
journalctl -u cron --since "1 hour ago"
- No line for your job at the expected time: the schedule or the crontab file is wrong (steps 3 and 4).
-
A line saying
CMD (...)but nothing happened: cron ran it and the command failed (steps 5 to 8).
3. Check the schedule syntax
A standard crontab line has five time fields, then the command:
# m h dom mon dow command
*/5 * * * * /home/app/bin/sync.sh
Common mistakes:
- Six fields. Standard cron has no seconds field.
0 */5 * * * *is valid in Spring or Quartz, but in crontab the sixth value becomes part of the command. - Day-of-month and day-of-week both set.
0 9 1 * 1runs on the 1st of the month and on every Monday, not only on Mondays that fall on the 1st. - Expecting
*/7in the minute field to mean "every 7 minutes forever". It restarts every hour, so you get :00, :07 … :56, then :00 again.
Paste the expression into a cron explainer if you are unsure, and confirm it says what you think.
4. Check which crontab you edited
There are several places cron reads from, and they use different formats:
-
crontab -eedits your user's crontab. No user column. -
/etc/crontaband files in/etc/cron.d/have an extra user column after the five time fields:0 3 * * * root /usr/local/bin/backup.sh. Leave it out and the job silently fails. - Files in
/etc/cron.d/are ignored if the file name contains a dot (on Debian-based systems), sobackup.cronis skipped whilebackupworks. They also need to be owned by root and not group- or world-writable. -
sudo crontab -eedits root's crontab, which is a different file from yours. Runcrontab -landsudo crontab -lto see both.
Also make sure the file ends with a newline. Some cron implementations ignore the last line if it doesn't.
5. “It runs manually but not in cron”: the environment
This is the big one. When you run a script in your terminal, you have your shell profile, your PATH, your virtualenv and your current directory. Cron has almost none of that. Its PATH is typically just /usr/bin:/bin, its shell is /bin/sh, and it starts in your home directory.
So a script that calls node, python3 from a virtualenv, aws, docker or anything in /usr/local/bin may simply not find the binary.
Fixes, in order of preference:
# Use absolute paths everywhere
*/5 * * * * /usr/local/bin/node /home/app/jobs/sync.js
# Or set PATH at the top of the crontab
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
If the script needs environment variables (API keys, DATABASE_URL), cron will not load them from .bashrc. Source a file explicitly or set them in the script.
To reproduce cron's environment in your terminal and see the real error:
env -i SHELL=/bin/sh PATH=/usr/bin:/bin HOME="$HOME" /bin/sh -c '/home/app/bin/sync.sh'
6. Relative paths and the working directory
A script that reads ./config.yml or writes logs/out.txt works from the project folder but not from cron, which starts in $HOME. Either cd first or use absolute paths:
0 * * * * cd /home/app && ./bin/report.sh
7. Percent signs and permissions
In a crontab line, % means "newline" and everything after the first one is sent to the command as standard input. This breaks date +%Y-%m-%d in a surprising way. Escape it as \%:
0 2 * * * /usr/bin/tar czf /backups/site-$(date +\%F).tgz /var/www
Then check the script itself: is it executable (chmod +x), does it start with a correct shebang (#!/usr/bin/env bash), and can the cron user read every file it touches?
8. Capture the output
By default, cron emails output to the local user, which on most servers means it disappears. Redirect it to a file so you can see what happened:
*/5 * * * * /home/app/bin/sync.sh >> /home/app/logs/sync.log 2>&1
The 2>&1 matters: most errors go to standard error. If you have a working mail setup, MAILTO=you@example.com at the top of the crontab sends output by email instead.
9. Time zone and reboot surprises
Cron uses the server's time zone. A job set for 0 9 * * * on a server in UTC runs at 9:00 UTC, not 9:00 where you are. Check with timedatectl. Some cron versions support CRON_TZ=Europe/Berlin at the top of the file.
@reboot jobs run when the cron daemon starts, which can be before the network or your database is ready. Add a short wait or a retry loop in the script.
The real problem: cron fails silently
Once your job works, there is a second issue: the next time it breaks, nothing will tell you. A crontab that stops gives no output and no error. The usual ways to cover that are:
- Log to a file and have something watch the file or the job's last run time.
- Use a "dead man's switch" style monitor that alerts when a job fails to check in.
- Move the schedule off the server entirely and have a hosted scheduler call an HTTP endpoint in your app, so retries, run history and alerts live in one place.
If you go the HTTP route, delay.run is one option: it calls your URL on a cron schedule, logs each response and alerts you by email, Slack or Discord when runs keep failing.
Whichever you choose, the checklist above will get the job running. Making sure you hear about it next time it stops is what saves you the Monday-morning surprise.
Top comments (0)