Cron Job Not Running? 14 Causes and How to Fix Each One

A step-by-step checklist for diagnosing why your cron job is not running. Covers PATH issues, permission failures, environment mismatches, and 11 other common causes, plus how to prevent it from happening again.

Start Free 20 checks free forever

Your cron job works when you run it manually. You paste the command into the terminal, it runs fine, output looks correct. But cron never fires it. Or it fires but nothing happens. Or it used to work and quietly stopped three weeks ago.

Before going through every possible cause, answer one question first: did cron even try to run your job? Check the system log:

grep CRON /var/log/syslog | tail -20

On RHEL/CentOS/AlmaLinux:

grep CRON /var/log/cron | tail -20

If your job appears in the log, cron tried to run it and something inside the script failed. If it doesn't appear, cron never attempted it. That distinction cuts the debugging time in half, because the causes are completely different.

Cron never tried to run the job

If there's no log entry for your job, the problem is before execution. Your cron job is not running because cron itself is stopped, can't read your crontab, or the schedule expression doesn't match when you think it does.

1. The cron daemon is stopped

Check whether the service is running:

# Debian/Ubuntu
systemctl status cron

# RHEL/CentOS/AlmaLinux
systemctl status crond

If it's inactive, start and enable it:

sudo systemctl enable --now cron

This is the rarest cause on managed servers (hosting providers keep cron running), but common on fresh VPS setups and Docker containers where cron isn't installed by default.

2. Wrong crontab or wrong user

Each user has their own crontab. If you edited root's crontab but your script needs to run as www-data, it won't have the right permissions. Check whose crontab you're editing:

# View current user's crontab
crontab -l

# View specific user's crontab
sudo crontab -u www-data -l

There's also the system crontab at /etc/crontab and drop-in files in /etc/cron.d/. These have an extra field for the username between the schedule and the command. If you put a user-format entry in the system crontab (or vice versa), cron silently ignores it.

3. Schedule expression is wrong

The classic: you meant every 5 minutes but wrote 5 * * * * (minute 5 of every hour) instead of */5 * * * * (every 5 minutes). Or you set 0 2 * * * and forgot that your server runs on UTC, so 2 AM UTC is 10 PM your time.

Another trap: combining day-of-month and day-of-week. 0 9 13 * 5 doesn't mean "Friday the 13th." Cron interprets it as "the 13th of any month OR any Friday," which fires far more often than expected.

Paste your expression into a cron validator or cron builder to see the next 5 run times. If they don't match your expectations, the expression is the problem.

4. Missing newline at end of crontab

If the last line of your crontab doesn't end with a newline character, cron may silently skip it. Most editors add one automatically, but if you're pasting content programmatically or using echo without -e, the trailing newline can get lost. Open the crontab with crontab -e and make sure there's a blank line after your last entry.

Cron ran the job, but it failed

If the log shows cron fired your job but nothing happened, the script itself is failing inside cron's environment. This is where most people get stuck, because the same command works in the terminal.

5. PATH is different in cron

This is the number one cause. Your terminal has a rich PATH variable loaded from .bashrc, .profile, and shell initialization files. Cron doesn't load any of that. Its default PATH is typically just /usr/bin:/bin.

A script that calls node, python3, composer, or wp without the full path works in your terminal but fails in cron because those binaries aren't in /usr/bin.

Fix it two ways. Either use absolute paths in your scripts and crontab entries:

*/5 * * * * /usr/bin/python3 /home/user/scripts/backup.py

Or set PATH at the top of your crontab:

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

*/5 * * * * python3 /home/user/scripts/backup.py

Find the full path of any binary with which python3 or type node.

6. Working directory is wrong

Cron doesn't run your job from the script's directory. It runs from the user's home directory (or / for system cron). Any relative path in your script, like ./data/output.csv or ../config/settings.json, resolves to the wrong location.

Fix it by adding cd at the start:

*/15 * * * * cd /var/www/app && /usr/bin/php artisan schedule:run

Or use absolute paths everywhere inside the script.

7. Missing permissions or shebang

If your crontab entry runs a script directly (not through an interpreter), the script needs the execute bit set and a shebang line:

chmod +x /home/user/scripts/backup.sh

And the first line of the script should be:

#!/bin/bash

Without the shebang, the system doesn't know which interpreter to use. Without the execute bit, it refuses to run the file at all. Neither produces an obvious error message in the cron log.

8. Percent signs in the command

Cron treats % as a newline character. A command like:

*/5 * * * * echo "Backup $(date +%Y-%m-%d)" >> /tmp/log.txt

breaks because cron splits it at each %. Escape them with backslashes:

*/5 * * * * echo "Backup $(date +\%Y-\%m-\%d)" >> /tmp/log.txt

Or move the command into a script file where percent signs don't need escaping.

9. Environment variables are missing

Cron runs with a minimal environment. Variables you set in .bashrc or .env files don't exist in cron's context. Database connection strings, API keys, HOME, LANG, NVM_DIR. None of them are available unless you explicitly set them.

You can replicate what cron sees by running your command in a stripped environment:

env -i /bin/bash -c '/home/user/scripts/backup.sh'

If it fails here, it will fail in cron too. Source your env file at the start of the crontab entry or inside the script:

*/5 * * * * . /home/user/.env && /usr/bin/php /var/www/app/cron.php

10. Output is hiding errors

Many cron guides tell you to redirect output to /dev/null:

*/5 * * * * /home/user/backup.sh > /dev/null 2>&1

This silences all output, including error messages. Your script crashes with a clear error, but you never see it because everything goes to /dev/null.

While debugging, redirect to a log file instead:

*/5 * * * * /home/user/backup.sh >> /home/user/cron-debug.log 2>&1

Check the log after the next scheduled run. Once the job works, you can switch back to /dev/null or set MAILTO in your crontab to receive error output by email:

[email protected]
*/5 * * * * /home/user/backup.sh

Cron emails the output (stdout + stderr) of every run to the address specified. No output, no email. If the job fails with an error, you get the error in your inbox.

Less obvious causes

11. Disk space is full

If the filesystem is full, cron can't write its temporary files and may fail to execute jobs. Check with df -h. A full /tmp or /var partition is a common culprit, especially on servers that don't rotate logs.

12. Overlapping runs

A job scheduled every 5 minutes that takes 7 minutes to complete creates overlapping processes. Two, three, four copies pile up, each consuming resources and potentially corrupting data. Use flock to prevent overlap:

*/5 * * * * flock -n /tmp/backup.lock /home/user/backup.sh

The -n flag makes it exit immediately if another instance holds the lock, instead of waiting.

13. SELinux or AppArmor blocking execution

On RHEL/CentOS with SELinux enabled, cron might be blocked from running scripts in certain directories. Check the audit log:

grep denied /var/log/audit/audit.log | grep cron

If SELinux is the cause, you'll see avc: denied entries. Fix by adjusting the file context or creating a policy exception. On Ubuntu with AppArmor, check /var/log/kern.log for similar denied messages.

14. Timezone mismatch

Cron uses the system timezone. If your server is set to UTC but you scheduled a job for "2 AM" expecting your local time, it will run at a different hour. Check with timedatectl. On some systems you can set the timezone per-crontab:

CRON_TZ=America/New_York
0 2 * * * /home/user/report.sh

Spring-forward DST transitions can also cause issues. Jobs scheduled between 2:00-2:59 AM in a US timezone skip entirely on the night clocks jump forward. Schedule critical jobs outside the 1-3 AM window, or use UTC.

Framework and CMS-specific cron issues

If you're running cron for a web application, the issues above still apply, but there are extra layers that can break things.

WordPress: WP-Cron is not a real cron. It fires only when someone visits the site. If you've disabled WP-Cron in favor of a real crontab, make sure the cron entry hits wp-cron.php over HTTP or runs wp cron event run --due-now via WP-CLI. A common mistake is pointing cron at the wrong URL after a site migration.

Laravel: Laravel's scheduler needs exactly one cron entry: * * * * * cd /path-to-project && php artisan schedule:run >> /dev/null 2>&1. If it's not running, check that the cron user has access to the project directory and that .env is readable. Monitoring Laravel cron catches silent failures in individual scheduled commands.

Drupal, Nextcloud, WHMCS, PrestaShop: These platforms run background tasks through a cron URL or CLI script. If cron triggers the URL over HTTP, the web server's max_execution_time can kill long-running tasks. CLI execution avoids this. We have dedicated guides for Drupal, Nextcloud, WHMCS, and PrestaShop cron setup.

Quick debugging checklist

Run through this in order. Most cron problems resolve within the first five checks.

  1. Is the cron daemon running? systemctl status cron
  2. Is the job in the right crontab? crontab -l (check user)
  3. Does the schedule expression match? Paste it in the validator
  4. Does the cron log show the job? grep CRON /var/log/syslog
  5. Is the PATH set? Use absolute paths or define PATH in crontab
  6. Is the working directory correct? Add cd /path && before the command
  7. Does the script have execute permissions and a shebang?
  8. Are there percent signs in the command? Escape with \%
  9. Is output going to /dev/null? Redirect to a log file while debugging
  10. Does the command work in a clean environment? Test with env -i

Stop debugging, start monitoring

This checklist fixes the current problem. It doesn't prevent the next one. A PHP upgrade, a server migration, a disk filling up. Any of these can break a working cron job without warning.

Heartbeat monitoring catches failures as they happen. Append a ping to your cron command:

*/5 * * * * /home/user/backup.sh && curl -fsS https://ping.watchcron.com/your-monitor-id > /dev/null

If the script fails or the server goes offline, the ping doesn't fire, and WatchCron sends an alert through Slack, Telegram, email, or any of 20+ channels. You find out in minutes, not three weeks later when someone notices stale backups.

For jobs that don't have a server at all, or where you'd rather not manage a crontab, Cloud Cron runs the job for you. Give it a URL and a schedule, and it handles the trigger and the monitoring in one step. No daemon to babysit, no PATH to debug, no crontab to lose during a migration.

Every cause on this page produces the same outcome: a cron job not running, with no alert telling you about it. You discover the problem from its consequences. Monitoring is the one fix that covers all of them at once.

WatchCron monitors your cron jobs and alerts you on failures. Free plan includes 20 monitors with Slack, Telegram, email, and 19 other channels.

Free plan · 20 checks forever Start Free

Frequently asked

Cron runs commands in a minimal environment without your shell's PATH, environment variables, or working directory. A command that works in your terminal fails in cron because it can't find the binary, is missing environment variables, or resolves relative paths to the wrong location. Use absolute paths, set PATH in your crontab, and add cd /path before your command.

Check the system log: grep CRON /var/log/syslog | tail -20 on Debian/Ubuntu, or grep CRON /var/log/cron | tail -20 on RHEL/CentOS. If your job appears, cron tried to run it and something inside the script failed. If it doesn't appear, cron never attempted it.

Yes. If the last line of your crontab doesn't end with a newline character, cron may silently skip it. Open the crontab with crontab -e and make sure there's a blank line after your last entry.

Redirect output to a log file instead: */5 * * * * /home/user/backup.sh >> /home/user/cron-debug.log 2>&1. Check the log after the next scheduled run. You can also set [email protected] in your crontab to receive error output by email.

Use flock to acquire an exclusive lock: */5 * * * * flock -n /tmp/backup.lock /home/user/backup.sh. The -n flag makes it exit immediately if another instance is already running, preventing duplicate processes from piling up.