You wrote a cron job. It runs fine when you execute it manually. But cron never runs it. Or it runs silently and produces nothing.
Welcome to cron debugging, where the same script works on your terminal and breaks in cron’s environment.
The Environment Problem
When you log in and run a command, you have:
- Your PATH (with /usr/local/bin, /opt/whatever, etc.)
- Your home directory
- Environment variables you’ve set in ~/.bashrc or ~/.zshrc
- Access to commands like
docker,python, custom scripts
Cron has:
- A minimal PATH:
/usr/bin:/bin - Limited environment variables
- No shell initialization
So your cron job tries to run docker and cron has no idea what that is.
Fix 1: Use Absolute Paths
This is the number one fix. Don’t do this:
0 2 * * * backup.shDo this:
0 2 * * * /usr/local/bin/backup.shOr find the absolute path:
which pythonwhich docker# /usr/bin/dockerThen use full paths in your cron job.
Fix 2: Set Your PATH
Add PATH to your crontab:
crontab -eAt the top, before any jobs:
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binSHELL=/bin/bash
0 2 * * * /usr/local/bin/backup.shNow cron knows where everything is.
Fix 3: Redirect Output to a Log File
Cron suppresses stdout. If your script prints something, you never see it. Redirect to a file:
0 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1The 2>&1 redirects errors too. Check the file:
tail -f /var/log/backup.logNow you can see what’s happening.
Fix 4: Set MAILTO (Or Don’t)
By default, cron emails you any output. If you don’t have mail set up, the output disappears into a black hole.
Disable mail:
MAILTO=""
0 2 * * * /usr/local/bin/backup.shOr send to a specific email:
MAILTO="admin@example.com"
0 2 * * * /usr/local/bin/backup.shBut honestly, redirecting to a log file is better.
Example: Backup Script
Bad (won’t work):
0 2 * * * backup.shBetter:
0 2 * * * /home/admin/scripts/backup.sh >> /var/log/backup.log 2>&1Good:
0 2 * * * /home/admin/scripts/backup.sh >> /var/log/backup.log 2>&1 && echo "Backup completed at $(date)" >> /var/log/backup.log || echo "Backup failed at $(date)" >> /var/log/backup.logTesting Your Cron Job
Before you schedule it, test it in cron’s environment:
# Run with cron's minimal environmentenv -i /bin/sh -c '/path/to/script.sh'
# Or see what PATH cron sees(crontab -l | grep -v ^# | head -1; echo 'echo $PATH') | at now + 1 minuteBetter yet, create a test cron that runs every minute:
* * * * * /usr/local/bin/backup.sh >> /tmp/test_backup.log 2>&1Wait a minute, check the log:
cat /tmp/test_backup.logOnce it works, change it to your desired schedule.
Common Cron Gotchas
Command not found
Your script uses docker but cron doesn’t know where it is.
Fix: /usr/bin/docker psNo $HOME
Cron doesn’t set your home directory. Scripts that reference ~/something break.
# Don't do thiscp ~/backup.conf /tmp/
# Do thiscp /home/admin/backup.conf /tmp/Commands with pipes or redirects fail
Cron runs jobs with /bin/sh -c, not /bin/bash. Some constructs don’t work.
# Make sure to explicitly call bash if needed0 2 * * * /bin/bash -c "docker ps | grep backup > /tmp/status.txt"Symlinks to scripts
Symlinks might not resolve in cron’s environment.
# Don't do this0 2 * * * ~/backup -> /home/admin/scripts/backup.sh
# Do this0 2 * * * /home/admin/scripts/backup.shScripts with relative paths
If your script changes directories, use absolute paths:
# Bad0 2 * * * cd /data && ./process.sh
# Good0 2 * * * cd /data && /data/process.shNo TTY
Cron runs without a terminal. Commands like sudo that need a TTY fail silently.
Use sudo’s NOPASSWD option in sudoers, or run cron as root.
A Real-World Cron Job
#!/bin/bashset -e
# LoggingLOG_FILE="/var/log/docker_cleanup.log"
{ echo "=== Docker cleanup started at $(date) ==="
# Clean up old images (7+ days old) docker image prune -a --filter "until=168h" -f
# Clean up dangling volumes docker volume prune -f
# Log disk usage echo "Disk usage after cleanup:" docker system df
echo "=== Docker cleanup completed at $(date) ==="} >> "$LOG_FILE" 2>&1Then in crontab:
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binMAILTO=""
# Run at 2 AM daily0 2 * * * /home/admin/scripts/docker_cleanup.shDebugging Active Cron Jobs
If a cron job is running but you don’t know why it’s failing:
# List all cron jobs for all userssudo grep CRON /var/log/syslog | tail -20
# Or on systemd systems:sudo journalctl -u cron | tail -50
# Check a specific user's crons:sudo crontab -u username -lThe Checklist
Before you schedule a cron job:
- Script uses absolute paths for all commands and files
- PATH is set in crontab (or all commands use full paths)
- Output redirects to a log file
- MAILTO is set to "" or a real email address
- Script has been tested manually:
/usr/local/bin/script.sh - Script has been tested in cron’s environment:
env -i /bin/sh -c '/usr/local/bin/script.sh' - Log file exists and is writable
- Cron time syntax is correct (use an online validator if unsure)
Cron jobs that fail silently are a special kind of frustrating. But they’re avoidable. Use absolute paths, redirect output, and test before you schedule. Your 3 AM self will thank you when the backup actually runs.