Why cron Is Not Running - Troubleshooting crontab on Linux
What you'll be able to do
- Work through why a cron job is not running, in order
- Read and write the schedule fields of a crontab line
- Avoid the classic PATH, permission, and relative-path traps
Prerequisites (read these first)
What You'll Learn
- You can work through why a cron job "isn't running" in a fixed order.
- You know where the logs are and how to read them.
- You can read and write the schedule fields of a crontab line.
- You can avoid the user, PATH, and permission traps that keep showing up in practice.
Quick Summary
When cron isn't working, do not fix it by feel. Check these in order:
- Is it actually registered?
- Whose crontab is it?
- Is there any trace of a run in the logs?
- Does it work when run by hand?
- PATH / permissions / executable problems
Prerequisites
- OS: Ubuntu
- Newcomers through early practitioners
- Both root and regular users are covered
One warning up front: do not run crontab -r
crontab -r deletes that user's entire crontab without asking for confirmation.
It sits right next to crontab -e (edit) on the keyboard, which makes "wiped it by a typo" a classic accident.
There is no built-in way to get the deleted content back.
- Always take a copy before editing:
crontab -l > ~/crontab-$(date +%Y%m%d).bak - To pause one job, run
crontab -eand put a#at the start of that line - If you really must delete, use
crontab -i -r(-iasks for confirmation first)
1. Kinds of cron (Misread This and You're Stuck)
Conclusion: User cron, root cron, and /etc/cron.* run as different users; know yours.
Ubuntu has more than one cron.
Settle the terms first.
| Term | One-line meaning | Other names you'll see |
|---|---|---|
| daemon | A program that keeps running in the background with no UI | Also "resident process" or "service" |
| cron | The daemon that runs commands automatically at set times | Also "cron daemon" or "crond" |
| crontab | The file holding the schedule, and the command that edits it | Same word for both meanings |
| job | One scheduled line inside a crontab | Also "entry" or "task" |
| MTA | The software responsible for sending mail | Postfix and sendmail are examples |
1-1. User cron
$ crontab -e
- Runs as: that user
- The one you use most
1-2. root cron
$ sudo crontab -e
- Runs as: root
- For work that needs privileges
1-3. /etc/crontab and /etc/cron.d
These are the system-wide definitions. The format has six fields: the five schedule fields are followed by a user column.
# min hour day month weekday user command
0 3 * * * root /usr/local/bin/backup.sh
Write it with five fields, as you would in a user crontab, and the command name gets read as the user name.
The log shows something like unknown user and nothing runs. The format depends on where you write it.
1-4. Directories such as /etc/cron.daily
Scripts placed in /etc/cron.hourly or /etc/cron.daily are executed by run-parts, which imposes two constraints.
- The execute bit is required (
chmod +x) - A filename containing a dot is ignored (
backup.shnever runs; drop the extension and name itbackup)
Be clear about where you wrote it, first.
2. Writing a crontab Line (the Five Schedule Fields)
Conclusion: Schedule fields are minute, hour, day, month, and weekday in that order.
One line of a user crontab (the one crontab -e opens) is five "when" fields plus the command to run.
/etc/crontab and /etc/cron.d take six fields, as shown in 1-3 — do not mix them up.
* * * * * command | | | | | | | | | +-- Day of week (0-7, 0 and 7 = Sunday) | | | +---- Month (1-12) | | +------ Day (1-31) | +-------- Hour (0-23) +---------- Minute (0-59)
* means "every one of them".
Examples:
0 3 * * *- Every day at 3:00 AM*/5 * * * *- Every 5 minutes0 0 * * 0- Every Sunday at midnight
When both "day of month" and "day of week" are set to something other than *, the job runs when either one matches.
0 3 1 * 1 fires on the 1st of every month and every Monday. To restrict to one of them, leave the other as *.
Inside a crontab command, an unescaped % is replaced with a newline.
Something like date +%Y%m%d works in the shell but breaks the moment you paste it into a crontab.
# Wrong (everything after % is cut off)
0 3 * * * /home/user/backup.sh >> /home/user/log/backup-$(date +%Y%m%d).log 2>&1
# Right (escape as \%)
0 3 * * * /home/user/backup.sh >> /home/user/log/backup-$(date +\%Y\%m\%d).log 2>&1
Moving the date handling into the script itself is an equally reliable way around it.
2-1. Confirm the line does what you meant
Rather than registering the real workload straight away, start with a line that only appends date every minute.
* * * * * /usr/bin/date >> /tmp/cron-test.log 2>&1
Wait a few minutes; if /tmp/cron-test.log keeps growing, cron itself is working.
Remove the line once you are done, or the log file grows forever.
3. Check Whether It Is Registered
Conclusion: Run crontab -l and sudo crontab -l; "I thought I registered it" tops the list.
$ crontab -l $ sudo crontab -l
crontab -l only prints what is registered. It changes nothing, so run it as often as you like.
"I thought I registered it" is the most common accident of all.
4. Checking Logs When Not Running
Conclusion: Check logs via grep CRON /var/log/syslog or journalctl -u cron to find runs.
4-1. Check cron logs
$ grep CRON /var/log/syslog | tail -n 50
Dec 15 03:00:01 web01 CRON[2451]: (user) CMD (/home/user/backup.sh) Dec 15 03:05:01 web01 CRON[2478]: (root) CMD (/usr/local/bin/cleanup.sh)
(user) is the user the job ran as, and CMD (...) is the command that was executed.
4-2. Real-time log tracking
$ tail -f /var/log/syslog | grep CRON
4-3. Using journalctl
$ sudo journalctl -u cron -n 100
No trace in the log means it never ran.
On systems without /var/log/syslog, use journalctl -u cron instead.
Both commands only display logs; neither changes the cron configuration.
5. When It Runs but Fails
Conclusion: A CMD line in the log means cron fired; run it by hand as that user next.
The log may contain a line like this:
CMD (/path/to/script.sh)
That means cron itself is working.
What to do next:
$ sudo -u <cron_user> /path/to/script.sh
Running it by hand actually performs whatever the script does. If the script deletes files or updates data, try it in a scratch directory, or read through it before you run it.
6. Trap #1: PATH Is Different
Conclusion: cron's PATH is tiny; use full paths or define PATH at the top of the crontab.
cron's PATH is extremely short. PATH is the list of directories searched when you type a bare command name. Your login shell and cron do not share the same list.
Wrong
mysqldump ...
Right
/usr/bin/mysqldump ...
which tells you the full path.
$ which mysqldump /usr/bin/mysqldump
Fixes:
- Write full paths
- Define PATH at the top of the crontab
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
7. Trap #2: No Execute Permission
Conclusion: Permission denied means the exec bit is missing; check ls -l and run chmod +x.
Permission denied
Check:
$ ls -l script.sh
Fix:
$ chmod +x script.sh
chmod +x leaves out who it applies to, so it normally grants execute to owner, group, and others at once.
To grant it to yourself only, name the target explicitly with chmod u+x script.sh.
8. Trap #3: Relative Paths
Conclusion: cron's working directory is not fixed, so always give absolute paths.
With cron, you cannot assume which directory it runs in.
Wrong
./script.sh
Right
/home/user/script.sh
Relative paths inside the script fail for the same reason. Make the file paths in the script absolute too.
9. Mail Notification (Making Failures Visible)
Conclusion: MAILTO delivers job output; no mail means no MTA, or cron never ran at all.
cron mails you whatever output a job produced. Error messages are output too, which is what makes this useful for spotting failures.
MAILTO=you@example.com
If no mail arrives:
- No MTA is configured
- Or cron never ran in the first place
Where mail is not an option, writing the result to a file is more dependable.
0 3 * * * /home/user/backup.sh >> /home/user/log/backup.log 2>&1
>> appends, and 2>&1 sends error output into the same file.
Point the output at a path the running user can write to.
Writing to /var/log/ from a regular user's cron fails with Permission denied, and the redirect takes the job down with it.
Use /var/log/ only from root's cron, or with a dedicated file you granted write permission on beforehand.
10. When cron Is the Cause of Heavy Load
Conclusion: Backups, compression, and rsync jobs are the usual suspects behind I/O spikes.
- Backups
- Log compression
- rsync
- Docker cleanup
Check whether the time load spiked lines up with the time these jobs run.
The quickest cross-check is journalctl -u cron --since "<time>" for the execution trace.
Things to Avoid
- Rewriting the job before reading the logs
- Putting everything into root's cron
- Omitting full paths
- Skipping the manual test run
- Running
crontab -rwithout a backup
11. Completion Checklist
Conclusion: Registration, log trace, output target, and a backup mean the setup is done.
- [ ]
crontab -lcontains the line you intended (also checksudo crontab -lfor root jobs) - [ ] The logs (
grep CRON /var/log/syslogorjournalctl -u cron) show a trace of a run - [ ] Every path in the command and inside the script is absolute
- [ ] You receive the result by file or by mail
- [ ] You kept a copy of the crontab from before the edit
Summary (Copy-Paste Template)
# Take a backup (always, before editing) crontab -l > ~/crontab-$(date +%Y%m%d).bak # Check crontab crontab -l sudo crontab -l # View logs grep CRON /var/log/syslog journalctl -u cron -n 100 # Run manually (as the cron user) sudo -u user /path/to/script.sh