Home › Guides › Your Cron Job Isn't Running: A Debugging Checklist That Starts With the Boring Stuff

Your Cron Job Isn't Running: A Debugging Checklist That Starts With the Boring Stuff

By Mohak Mangal · Published October 26, 2026 · 8 min read

Few things are more irritating than a cron job that works perfectly when you run it by hand and does absolutely nothing when cron runs it. No error, no email, no log line. Just silence.

The good news is that the causes are a short, well-worn list. Work through it in this order, because the first few take thirty seconds each and catch most cases.

1. Is cron even running the line?

Before blaming your script, find out whether cron tried. On Debian and Ubuntu, grep CRON /var/log/syslog shows each execution. On systems that use journald, try journalctl -u cron (the unit is called crond on Red Hat style systems). If you see your command listed at the expected minute, cron did its job and the problem is inside the command. If you see nothing, the schedule or the crontab file is the problem.

2. The percent sign

This one is cruel because it looks so harmless. In a crontab, an unescaped % is treated as a newline, and everything after the first one is fed to the command as standard input. So this line is quietly broken:

0 2 * * * tar czf /backup/site-$(date +%F).tgz /var/www

It looks innocent, but date +%F contains a percent sign. The fix is to escape it: date +\%F. Or, cleaner, move the logic into a script file and call the script. Scripts don't have this problem.

3. The tiny environment

Cron doesn't read your .bashrc or .profile. On many systems its PATH is just /usr/bin:/bin. So a command that you type happily (node, python3 from a custom install, anything in /usr/local/bin) may not be found. Use absolute paths, or set PATH=... at the top of the crontab.

Cron also uses /bin/sh by default, not Bash. If your script relies on Bash features like [[ ]] or arrays and it has no #!/bin/bash line, run it through bash script.sh explicitly, or set SHELL=/bin/bash in the crontab.

4. Permissions on the script

If you call the script directly, it needs the execute bit: chmod +x script.sh. Also remember that the job runs as whichever user owns the crontab. A script that reads files only your login user can access will fail when it runs under root's crontab, or the reverse. See our permissions guide if the octal numbers still feel foreign.

5. The last line needs a newline

Some cron implementations ignore the final line of a crontab if the file doesn't end with a newline character. If you edit with crontab -e, your editor normally handles this. If you build the file with a script or echo and install it, make sure it ends with a blank line.

6. Wrong crontab, wrong format

There are several places a schedule can live, and they don't all use the same format:

  • Your user crontab (crontab -e): five time fields, then the command.
  • /etc/crontab and files in /etc/cron.d/: five time fields, then a username, then the command. Forget the username and cron treats your command's first word as the user.
  • /etc/cron.daily, cron.hourly and friends: these hold plain scripts, not crontab lines. On Debian-based systems the filenames must not contain dots, or run-parts skips them.

It's worth running crontab -l to confirm that what you think you installed is actually there, and that you're looking at the right user.

7. Time zone surprises

Cron schedules in the server's local time. A cloud machine is often on UTC. If you wanted 9:00 in India but the server is on UTC, then 0 9 * * * fires at 14:30 IST. Run date on the server and compare. If you're not sure when a line will next fire, paste it into the cron expression explainer and compare the listed times with the server clock.

8. The job ran, but you can't see why it failed

By default, cron mails any output to the local user, which on most modern servers goes nowhere you'll ever read. Send it to a file instead:

*/10 * * * * /home/app/sync.sh >> /var/log/sync.log 2>&1

The 2>&1 part sends error messages to the same file. Add this first, wait for one run, and read the log. Half of all cron mysteries solve themselves at this step.

9. Overlap, locks and long runs

If a job takes twelve minutes and runs every ten, two copies will fight over the same files. Wrap it in flock: flock -n /tmp/sync.lock /home/app/sync.sh. The -n flag makes a second copy give up immediately rather than queue behind the first.

A quick way to separate "cron problem" from "script problem"

Temporarily change the schedule to * * * * * and the command to something trivial: date >> /tmp/crontest.txt. If the file fills up, cron works and your real command is at fault. If it doesn't, the issue is in the crontab itself. Put the schedule back afterwards. Seriously, don't forget this part. Running a real backup every minute is a lesson people only need once.

When cron is the wrong tool

If a task needs retries, dependency ordering, or a record of every run, a systemd timer or a proper job scheduler will serve you better. Cron is a sharp, simple tool. It won't tell you when it fails unless you build that in.

Free tool
Try it now: Cron Expression Explainer & Next Run Calculator
Decode any 5-field cron expression and see its next scheduled run times.

Keep reading