Why cron Is Not Running - Troubleshooting crontab on Linux

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:

  1. Is it actually registered?
  2. Whose crontab is it?
  3. Is there any trace of a run in the logs?
  4. Does it work when run by hand?
  5. PATH / permissions / executable problems

Prerequisites

  • OS: Ubuntu
  • Newcomers through early practitioners
  • Both root and regular users are covered

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.sh never runs; drop the extension and name it backup)

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 minutes
  • 0 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 ...
/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 -r without a backup

11. Completion Checklist

Conclusion: Registration, log trace, output target, and a backup mean the setup is done.

  • [ ] crontab -l contains the line you intended (also check sudo crontab -l for root jobs)
  • [ ] The logs (grep CRON /var/log/syslog or journalctl -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

Next Reading

Share this article

Next steps