Cron Job Not Running? 12 Common Causes & Fixes
You set up a cron job. It does not run. There is no error message, no log entry, nothing. This is one of the most frustrating debugging experiences in system administration because cron fails silently by default. This guide walks through every common cause, in order of likelihood, with the exact commands to diagnose and fix each one.
Quick Diagnostic Checklist
Run these three commands first. They catch 80% of cron issues:
# 1. Is the cron daemon running? systemctl status cron # Debian/Ubuntu systemctl status crond # CentOS/RHEL # 2. What's in your crontab? crontab -l # 3. What do the cron logs say? grep CRON /var/log/syslog | tail -20 # Debian/Ubuntu grep CRON /var/log/cron | tail -20 # CentOS/RHEL journalctl -u cron --since "1 hour ago" # systemd
PATH Is Not Set
This is the number one cause of cron jobs that "work when I run them manually but not in cron." When you run a command in your terminal, your shell has a rich PATH variable that includes directories like /usr/local/bin, /home/user/.nvm/bin, etc. Cron uses a minimal PATH, typically just /usr/bin:/bin.
Fix: Use absolute paths for all commands, or set PATH explicitly at the top of your crontab:
# Set PATH at the top of crontab PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin # Or use absolute paths in each job 0 2 * * * /usr/bin/python3 /home/deploy/scripts/backup.py 0 * * * * /usr/local/bin/node /home/deploy/app/cleanup.js
To find the absolute path of any command, run which python3 or which node in your terminal.
Permission Denied on the Script
Your script needs execute permission, or the cron job needs to invoke the interpreter explicitly. A common scenario: you created a script and forgot chmod +x.
Fix:
# Option A: Make the script executable chmod +x /home/deploy/scripts/backup.sh # Option B: Call the interpreter explicitly (no chmod needed) 0 2 * * * /bin/bash /home/deploy/scripts/backup.sh 0 3 * * * /usr/bin/python3 /home/deploy/scripts/report.py
Also check file ownership. If the crontab belongs to user deploy but the script is owned by root with mode 700, the cron job will fail silently.
Syntax Error in the Cron Expression
A subtle syntax error in the cron expression can make the job never match. Common mistakes include using 6 fields instead of 5, confusing day-of-month and day-of-week positions, or using unsupported characters.
Fix: Validate your expression with our Cron Expression Generator to preview the next execution times before committing it to your crontab.
# WRONG: 6 fields (seconds field is not supported by system cron) */30 * * * * * /usr/bin/php /var/www/task.php # CORRECT: 5 fields */30 * * * * /usr/bin/php /var/www/task.php # WRONG: L (last) and W (weekday) are NOT supported in Vixie cron 0 0 L * * /usr/bin/php /var/www/monthly.php # CORRECT: Use a workaround or check in the script 0 0 28-31 * * [ "$(date +%d -d tomorrow)" == "01" ] && /usr/bin/php /var/www/monthly.php
Environment Variables Are Missing
Cron runs jobs in a stripped-down environment. Variables you set in .bashrc, .profile, or .env files are not available. Your script tries to read DATABASE_URL, gets nothing, and fails.
Fix: Define variables in the crontab itself, source them in the command, or use a wrapper script:
# Option A: Define at the top of crontab DATABASE_URL=postgresql://user:pass@localhost/mydb API_KEY=sk-abc123 0 * * * * /usr/bin/python3 /home/deploy/sync.py # Option B: Source a .env file in the command 0 * * * * . /home/deploy/.env && /usr/bin/python3 /home/deploy/sync.py # Option C: Use env in a wrapper script #!/bin/bash source /home/deploy/.env cd /home/deploy python3 sync.py
To see exactly what environment cron provides, add this temporary job:
* * * * * env > /tmp/cron_env.txt
Wrong User's Crontab
Every user has their own crontab. If you added a job to root's crontab but the script needs to run as www-data, or vice versa, the job either runs with wrong permissions or accesses the wrong files.
Fix: Verify which user's crontab you are editing:
# Check current user whoami # List current user's crontab crontab -l # List a specific user's crontab (requires root) crontab -u www-data -l # Edit a specific user's crontab sudo crontab -u www-data -e # Also check the system crontab (different format with user field) cat /etc/crontab
Note: /etc/crontab has a sixth field specifying the user. Per-user crontabs (crontab -e) do not. Mixing up these formats is a common source of errors.
Timezone Confusion
Your cron expression says 0 9 * * * (9 AM). But 9 AM in which timezone? System cron uses the server's timezone. If your server is in UTC and you expected local time, your job runs hours earlier or later than intended.
Fix: Check the server's timezone and either adjust your schedule or change the timezone:
# Check current timezone timedatectl # or cat /etc/timezone # Set timezone for cron (add to top of crontab) CRON_TZ=America/New_York 0 9 * * * /home/deploy/scripts/morning-report.sh # Or convert manually: 9 AM Eastern = 14:00 UTC (winter) 0 14 * * * /home/deploy/scripts/morning-report.sh
The CRON_TZ variable is supported by some cron implementations (like Vixie cron on many Linux distributions) but not all. If your system does not support it, convert to UTC manually or use a scheduling service with per-job timezone support.
Tired of debugging silent cron failures?
CronJobPro logs every execution with status codes, response times, and output. When a job fails, you get notified instantly via email or webhook. No more guessing.
Try CronJobPro FreeMail System Overflow
By default, cron tries to email the output of every job to the local user. If the local mail system is broken or the mailbox is full, cron may stop running new jobs. On some systems, a full /var/spool/mail/ can also fill the disk partition.
Fix: Redirect output explicitly and suppress cron mail:
# Redirect stdout and stderr to a log file 0 2 * * * /home/deploy/backup.sh >> /var/log/backup.log 2>&1 # Or suppress output entirely 0 2 * * * /home/deploy/backup.sh > /dev/null 2>&1 # Disable mail for all jobs (add to top of crontab) MAILTO=""
Overlapping Job Runs
If a job takes longer than the schedule interval, multiple instances run simultaneously. This can cause database locks, corrupted files, or resource exhaustion. The new instance might also fail because the previous one holds a lock, making it look like the job "doesn't work."
Fix: Use flock to prevent overlapping runs:
# Use flock to skip the job if the previous run is still active */5 * * * * /usr/bin/flock -n /tmp/sync.lock /home/deploy/sync.sh # -n = non-blocking (skip if locked) # The lock file is automatically released when the script exits
Disk Full
When the disk is full, cron cannot write to its spool directory, temporary files, or log files. Some cron implementations stop scheduling entirely when there is no disk space. Your script might also fail because it cannot write output files or temp data.
Fix:
# Check disk usage df -h # Find large files eating space du -sh /var/log/* | sort -rh | head -10 # Check if log rotation is working ls -la /var/log/*.gz # Common disk hogs: # - /var/log/ — unrotated logs # - /var/spool/mail/ — cron output mail # - /tmp/ — leftover temp files # - Docker: /var/lib/docker/
The Cron Daemon Is Not Running
On some systems, especially minimal Docker containers, VPS instances, or after an OS upgrade, the cron service might be stopped or not installed at all.
Fix:
# Check if cron is running systemctl status cron # Debian/Ubuntu systemctl status crond # CentOS/RHEL # Start it sudo systemctl start cron sudo systemctl enable cron # Start on boot # On Alpine Linux (no systemd) rc-service crond status rc-service crond start
Missing Newline at End of Crontab
This one catches people constantly. The cron specification requires that each crontab file ends with a newline character. If the last line of your crontab does not have a trailing newline, that last job will be silently ignored.
Fix: Always ensure your crontab ends with a blank line:
0 2 * * * /home/deploy/backup.sh 0 * * * * /home/deploy/sync.sh # This blank line below is required:
Special Characters Not Escaped
The percent sign (%) has special meaning in crontab. It is treated as a newline character, and everything after the first % is sent as stdin to the command. This commonly breaks date commands.
Fix: Escape percent signs with a backslash:
# WRONG: % will break this 0 2 * * * /usr/bin/pg_dump mydb > /backups/db_$(date +%Y%m%d).sql # CORRECT: Escape the % characters 0 2 * * * /usr/bin/pg_dump mydb > /backups/db_$(date +%Y%m%d).sql # ALTERNATIVE: Put the command in a script (no escaping needed) 0 2 * * * /home/deploy/scripts/backup.sh
Step-by-Step Debugging Procedure
If you have gone through all 12 causes and your job still does not run, follow this systematic procedure:
- 1Add a trivial test job that writes a timestamp to a file every minute. If this does not work, the problem is with cron itself, not your script.
* * * * * echo "cron works $(date)" >> /tmp/cron-test.txt - 2Run your exact command manually as the same user cron will use. Switch users with
sudo -u deploy bashand then paste the command from your crontab. - 3Run with cron's environment. Create a script that sources the minimal cron environment and then runs your command. Compare behavior with your interactive shell.
- 4Capture all output. Add
>> /tmp/job.log 2>&1to your crontab entry and inspect the log after the next scheduled run. - 5Check cron logs for any rejection messages using the commands from the diagnostic checklist above.
Avoid These Problems Entirely
Most of these 12 issues exist because system cron was designed in 1979 for a different era. It has no logging, no monitoring, no alerting, and no web interface. Every failure is silent by default.
If your cron job calls an HTTP endpoint (which is the case for most web application tasks), an external scheduling service eliminates these problems by design:
| Problem | System Cron | CronJobPro |
|---|---|---|
| PATH issues | Common, manual fix | N/A (HTTP request) |
| Silent failures | Default behavior | Email/webhook alert |
| No execution log | Must configure manually | Full history dashboard |
| Timezone confusion | Server timezone | Per-job timezone setting |
| Server goes down | Jobs stop | Still attempts, alerts you |
Related Articles
Fix timezone-related scheduling problems in cron.
Cron Job Monitoring Best PracticesMonitor cron jobs with heartbeats, logs, and alerts.
Docker Cron Jobs GuideRun cron jobs in Docker containers without common pitfalls.
Cron vs Systemd TimersCompare cron and systemd timers for Linux scheduling.
Never debug a silent cron failure again
CronJobPro monitors every execution and alerts you instantly when something fails. Full logs, response codes, and timing data for every run. Free for up to 5 jobs.