Troubleshooting Cron Jobs
One of the most frustrating aspects of cron is that when something goes wrong, there's often little feedback. The job simply doesn't run, leaving you to investigate why. Systematic debugging can quickly identify the issue.
Step 1: Verify Cron Expression Syntax
Start by verifying that your cron expression is valid.
Common Syntax Errors
An invalid cron expression won't run:
# Invalid - only 4 fields instead of 5
0 2 * *
# Valid - 5 fields
0 2 * * *
Test your cron expression using an online validator like crontab.guru or with the crontab -e editor, which will highlight syntax errors.
Check Crontab Format
# View your crontab
crontab -l
# Look for your job
# It should be in format: minute hour day month dayofweek command
If the crontab command doesn't appear, it doesn't exist.
Step 2: Verify Cron Daemon is Running
Cron jobs won't run if the cron daemon isn't running.
# Check if cron daemon is running
systemctl status cron
# or
service cron status
# On macOS
sudo launchctl list | grep cron
# Start cron if not running
sudo systemctl start cron
If cron isn't running, start it:
sudo systemctl start cron
sudo systemctl enable cron # Enable on reboot
Step 3: Check Cron User Permissions
Your cron jobs run under your user account, and you must have permission to use cron.
# Check if you're allowed to use cron
ls -l /etc/cron.allow /etc/cron.deny 2>/dev/null
# If /etc/cron.allow exists, you must be listed there
cat /etc/cron.allow
# If /etc/cron.deny exists, you must NOT be listed there
cat /etc/cron.deny
If you're not in cron.allow or you're in cron.deny, add yourself:
# Add user to cron.allow
sudo usermod -a -G cron username
# or
sudo echo "username" >> /etc/cron.allow
Step 4: Verify the Command Exists and Works
The most common issue: the command in your cron job doesn't exist or fails when run.
Test the command manually:
# If your crontab contains
0 2 * * * /backup/scripts/daily-backup.sh
# Test it directly
/backup/scripts/daily-backup.sh
# Check exit code (0 = success)
echo $?
If the command fails manually, it will fail in cron too.
Check Script Shebang
Scripts must have a proper shebang line:
#!/bin/bash
# ^ This is required
# Your script content
Without a shebang, the script might fail in cron if the shell isn't specified.
Make Script Executable
# Check if script is executable
ls -l /backup/scripts/daily-backup.sh
# Should show -rwxr-xr-x (x = executable)
# Make it executable if needed
chmod +x /backup/scripts/daily-backup.sh
Step 5: Use Absolute Paths
Cron doesn't have the same PATH environment variable as your shell. Use absolute paths for everything.
# BAD - uses PATH to find commands
0 2 * * * backup.sh
# GOOD - explicit path
0 2 * * * /backup/scripts/daily-backup.sh
# BAD - relies on implicit PATH for commands
0 2 * * * mysqldump > backup.sql
# GOOD - explicit paths
0 2 * * * /usr/bin/mysqldump > /backups/backup.sql
Check Script's Dependencies
# If script uses commands, verify they exist at absolute paths
# In your script, use absolute paths:
#!/bin/bash
/usr/bin/mysqldump ...
/bin/gzip ...
/usr/bin/mail ...
Or explicitly set PATH at the beginning of your crontab:
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
0 2 * * * /backup/scripts/daily-backup.sh
Step 6: Check Cron Logs
Enable and check cron logging:
Linux Cron Logs
# View cron logs (location varies by system)
tail -f /var/log/cron # CentOS/RHEL
tail -f /var/log/syslog # Debian/Ubuntu
tail -f /var/log/system.log # macOS
# Search for your specific job
grep "backup" /var/log/cron
# Follow logs in real-time
sudo journalctl -u cron -f
Enable More Verbose Cron Logging
# Edit syslog config
sudo nano /etc/rsyslog.d/50-default.conf
# Uncomment or add cron logging
# cron.* /var/log/cron.log
# Restart rsyslog
sudo systemctl restart rsyslog
Parse Cron Logs
Look for:
- "CROND[PID]: (username) CMD (command)" - job started
- Error messages explaining why job failed
- "Unauthorized" - permission issues
- "MALFORMED CRONTAB" - syntax error
Step 7: Redirect Output and Errors
Make your cron job log output for debugging:
# Log all output
0 2 * * * /backup/scripts/daily-backup.sh >> /var/log/backup.log 2>&1
# Separate stdout and stderr
0 2 * * * /backup/scripts/daily-backup.sh >> /var/log/backup.log 2>> /var/log/backup-errors.log
# Email output if job fails
0 2 * * * /backup/scripts/daily-backup.sh 2>&1 | mail -s "Backup Report" [email protected]
In your script, explicitly log:
#!/bin/bash
LOG_FILE="/var/log/backup.log"
echo "[$(date)] Backup started" >> "$LOG_FILE"
# Your backup commands
mysqldump ... >> "$LOG_FILE" 2>&1
if [ $? -eq 0 ]; then
echo "[$(date)] Backup succeeded" >> "$LOG_FILE"
else
echo "[$(date)] Backup failed" >> "$LOG_FILE"
exit 1
fi
Step 8: Test Timing
Verify that the time in your cron expression is correct and accounts for time zones.
# Check current time and timezone
date
timedatectl # Linux
systemsetup -gettimezone # macOS
# Is your server in UTC?
# Test by scheduling something 1 minute in the future
* * * * * echo "test" >> /tmp/cron-test.log
# Wait a minute, check /tmp/cron-test.log
Step 9: Check Environment Variables
Cron runs with minimal environment variables. Variables set in your shell (.bashrc, .bash_profile) aren't available to cron.
# See what environment cron has
0 * * * * env | sort > /tmp/cron-env.txt
# Compare with your shell
env | sort > /tmp/shell-env.txt
diff /tmp/shell-env.txt /tmp/cron-env.txt
Export required variables at the top of your crontab:
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
HOME=/home/username
[email protected]
0 2 * * * /backup/scripts/daily-backup.sh
Step 10: Run as Root if Needed
If your job needs root privileges:
# View root's crontab (requires sudo)
sudo crontab -l
# Edit root's crontab
sudo crontab -e
# Add your job to root's crontab
0 2 * * * /backup/scripts/daily-backup.sh
Be cautious: root cron jobs can have high impact. Test thoroughly.
Step 11: Check Disk Space
Some jobs fail silently if there's no disk space:
# Check disk usage
df -h
# Check if output directory has space
du -sh /var/log
du -sh /backups
If disk is full, the job can't write output and fails silently.
Step 12: Test with Explicit Time
Set up a temporary cron job to run a few minutes in the future:
# Current time: 14:35
# Schedule for 14:37
37 14 * * * echo "test" >> /tmp/cron-test.log 2>&1
# Wait for the time to pass
# Check /tmp/cron-test.log
This confirms cron is running and can execute commands.
Complete Debugging Checklist
# 1. Validate syntax
crontab -e # Editor should not show errors
# 2. Verify daemon
systemctl status cron
# 3. Check logs
tail -f /var/log/cron
# 4. Test command
/backup/scripts/daily-backup.sh
# 5. Check permissions
ls -l /backup/scripts/daily-backup.sh
# Should show executable (x)
# 6. Verify paths
which mysqldump # Ensure command exists
# Use absolute path
# 7. Check environment
0 * * * * env > /tmp/cron-env.log
# 8. Verify time
date
timedatectl
# 9. Test with explicit output
0 * * * * echo "test" >> /tmp/cron-test.log 2>&1
# 10. Check disk space
df -h
Common Issues and Solutions
| Issue | Cause | Solution |
|---|---|---|
| Command not found | Relative path used | Use absolute path |
| Permission denied | File not executable | chmod +x script |
| No output | Paths/variables wrong | Add logging with absolute paths |
| Runs but fails | Dependencies missing | Verify all commands exist |
| Wrong time | Timezone/syntax | Check date, validate expression |
| MALFORMED CRONTAB | Syntax error | Check for exactly 5 fields |
| Not running at all | Cron daemon stopped | systemctl start cron |
Systematic debugging typically reveals the issue quickly. The most common problems are wrong paths, missing permissions, and syntactical errors. By following these steps, you can identify and fix almost any cron issue.
