Cron Not Running? 7 Reasons Your Scheduled Jobs Fail Silently
It works when you run it manually. Then you check the logs at 3 AM and realize the cron job hasn't executed in days.
This is one of the most common production nightmares I've seen in 20+ years of field engineering. The job looks fine in your head, runs fine in your terminal, but the scheduler ignores it completely. Let me walk you through the seven places I always check first.
1. The PATH Trap
This is the #1 culprit. Cron runs with a minimal PATH:
echo $PATH
# Usually: /usr/bin:/bin
When you run a command manually, your shell might have /usr/local/bin, /opt/bin, or even Python installed in a non-standard location. Cron doesn't.
# This fails if python3 is in /usr/local/bin
* * * * * python3 /opt/scripts/cleanup.py >> /var/log/cleanup.log
Fix: Use absolute paths for everything, or set PATH explicitly in the crontab:
PATH=/usr/local/bin:/usr/bin:/bin
* * * * * /usr/local/bin/python3 /opt/scripts/cleanup.py >> /var/log/cleanup.log
2. Environment Variables Vanish
Your script might rely on environment variables set in .bashrc or .profile. Cron doesn't source these files.
# This breaks if $API_KEY is set in your .bashrc but not in cron
* * * * * /opt/scripts/fetch_data.sh
Fix: Export variables in the crontab itself:
* * * * * export API_KEY="your_key_here" && /opt/scripts/fetch_data.sh
Or better yet, use a .env file and source it in the script:
#!/bin/bash
set -a
source /opt/scripts/.env
set +a
3. Working Directory Changes
Cron always runs from the user's home directory ($HOME), not the directory where the script lives. If your script uses relative paths, it will fail silently or write to the wrong place.
# This fails if you're not in /opt/scripts when cron runs
* * * * * cd /opt/scripts && ./process_logs.sh
Fix: Always use absolute paths, or force the directory change at the start of the command:
* * * * * (cd /opt/scripts && ./process_logs.sh) >> /var/log/process.log 2>&1
4. Output Goes Nowhere (And You Miss It)
By default, cron sends output to the user's email. If mail isn't configured, output is discarded. Your job might be failing every day and you'd never know.
# Silent failure: errors go to nowhere
* * * * * /opt/scripts/broken_job.sh
Fix: Always redirect output to a log file:
* * * * * /opt/scripts/broken_job.sh >> /var/log/cron/job.log 2>&1
Check the log daily, or set up monitoring to alert on errors:
tail -f /var/log/cron/job.log | grep --line-buffered "ERROR"
5. Permissions Are Wrong
The cron user might not have permission to read the script, write to the log, or access the resources the script needs.
# Check if the script is executable
ls -la /opt/scripts/broken_job.sh
# Should show: -rwxr-xr-x
Fix: Ensure the script is executable and owned by the right user:
chmod +x /opt/scripts/broken_job.sh
chown deploy:deploy /opt/scripts/broken_job.sh
Also check SELinux or AppArmor profiles that might block execution.
6. The System Is Asleep
If your server is a laptop, VM, or cloud instance that can hibernate, cron jobs won't run during downtime. On systemd systems, check if suspend.target or hibernate.target is enabled:
systemctl status systemd-suspend.service
systemctl status systemd-hibernate.service
Fix: Use systemd.path or anacron for systems that aren't always on:
# Anacron runs missed jobs when the system boots
sudo apt install anacron
Or set up a systemd timer that checks system uptime:
# /etc/systemd/system/myjob.timer
[Timer]
OnBootSec=5min
OnUnitActiveSec=24h
AccuracySec=1min
[Install]
WantedBy=timers.target
7. Hidden Syntax Errors in the Crontab
A single extra space, a missing newline, or a malformed time field can cause cron to skip the entire line. Common mistakes:
# Wrong: five fields, not six
* * * * * * command # Too many fields
# Wrong: month/day confusion
0 9 * * 1 command # This runs at 9 AM on Mondays (1=Mon)
0 9 1 * * command # This runs at 9 AM on the 1st of every month
# Wrong: missing newline at end of file
* * * * * /opt/scripts/final_job.sh # Some cron implementations need a trailing newline
Fix: Validate your crontab with crontab -l and review each field carefully:
# List current crontab
crontab -l
# Validate syntax (GNU cron)
crontab -e # Use a linter or checker if available
The Diagnostic Checklist
When a cron job fails silently, run this checklist in order:
# 1. Check if cron is running
systemctl status cron
# 2. Check the log
sudo tail -100 /var/log/syslog | grep CRON
# 3. Test the command manually with the same environment
sudo -u deploy env -i HOME=/home/deploy SHELL=/bin/bash PATH=/usr/bin:/bin /opt/scripts/broken_job.sh
# 4. Check permissions
ls -la /opt/scripts/broken_job.sh
ls -la /var/log/cron/job.log
# 5. Verify the crontab syntax
crontab -l | cat -A # Show hidden characters
The Bottom Line
Cron is powerful but unforgiving. It doesn't send emails by default, it ignores your environment, and it runs from a fixed directory. The seven reasons above cover 90% of the silent failures I've seen in production.
Next time your scheduled job vanishes at 3 AM, check these seven spots before you pull your hair out.
More ops notes and server reliability guides: jiunotes · ops notes
Top comments (1)
Good checklist — the PATH trap and the missing env vars have bitten me more than once. The failure mode I'd add as number eight: the job runs, exits 0, and does nothing. Cron has no 'did it actually run' signal, and a Laravel scheduled job that fires but finds zero rows to process looks exactly like success. I now have every scheduled job touch a heartbeat on completion — a last-run timestamp or a ping to a monitor — and alert on the heartbeat going stale instead of on errors. Absence of a run is the alert; the log only tells you what happened when something happened.