🔍 Cron Troubleshooting

Cron & Scheduling Series: Part 1 — Cron Deep Dive  |  Part 2 — Systemd Timers  |  Part 3 — at & anacron  |  Part 4 — Troubleshooting

Why Cron Jobs Fail

Cron jobs fail silently more often than any other scheduled task system. No popup, no error on screen — the job just does not run and nobody notices until something important is missing. The failures almost always come down to a small set of root causes: PATH issues, environment differences, permission problems, syntax errors, or the cron daemon not running.

This page gives you a systematic diagnostic workflow and covers every common failure pattern so you can fix cron problems quickly and confidently.

The Diagnostic Workflow

  1. Is crond running?systemctl status crond
  2. Is the job in the crontab?crontab -l or check /etc/cron.d/
  3. Is the syntax correct? — Verify with crontab.guru
  4. Is it logging anything?journalctl -u crond or /var/log/cron
  5. Does it run manually? — Run as the cron user with cron's environment
  6. PATH/environment issue? — Use full paths, check env differences
  7. Permission issue? — Check script is executable, user has access
  8. Output going somewhere? — Add logging to the job

Examples

1
Check Cron Logs
### RHEL / Rocky / AlmaLinux ###

# Check crond daemon logs via journalctl
sudo journalctl -u crond
sudo journalctl -u crond --since today
sudo journalctl -u crond -n 50
sudo journalctl -u crond -f    # follow live

# Traditional log file (if rsyslog is writing it)
sudo tail -50 /var/log/cron
sudo grep "$(date +%b\ %e)" /var/log/cron   # today's entries

### Debian / Ubuntu ###

sudo grep CRON /var/log/syslog | tail -30
sudo grep CRON /var/log/syslog | grep "$(date +%b\ %e)"
Typical /var/log/cron entries:
Apr 25 02:00:01 server CROND[12345]: (root) CMD (/usr/local/bin/backup.sh) Apr 25 02:00:02 server CROND[12345]: (root) CMDOUT (tar: Removing leading...) Apr 25 03:00:01 server CROND[12346]: (alice) CMD (/home/alice/sync.sh) Apr 25 03:00:01 server CROND[12346]: (CRON) error (can't open /home/alice/sync.sh)
💡 The cron log shows CMD entries for every job that fires. If you see a CMD entry but the job appears to have done nothing, the script ran but failed silently. If you see no CMD entry at all for a job that should have run, cron never triggered it — check syntax and crontab location.
2
Test a Script in Cron's Environment

The most reliable way to reproduce cron failures — simulate cron's exact environment before the job runs:

# Method 1: Run as the cron user with a minimal environment
sudo -u root env -i HOME=/root \
    PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \
    SHELL=/bin/sh \
    /bin/sh /usr/local/bin/myscript.sh

# Method 2: Schedule a 1-minute test job that dumps its environment
* * * * * env > /tmp/cron-env.txt 2>&1
# Wait 1 minute, then:
cat /tmp/cron-env.txt

# Method 3: Wrap the script with explicit environment
* * * * * /bin/bash -c 'source /etc/profile; /usr/local/bin/myscript.sh' >> /tmp/test.log 2>&1

# Check what environment your interactive shell has vs cron
echo $PATH           # your PATH
cat /tmp/cron-env.txt | grep PATH   # cron's PATH
⚠️ This is the number one cron fix. If a script works in your shell but not in cron, the cause is almost always the environment. Cron runs with PATH=/usr/bin:/bin and SHELL=/bin/sh. Your interactive shell has a much richer environment. Test in cron's environment before concluding the script is broken.
3
Common Failure: Script Not Executable
# Symptom in cron log:
# CROND: (root) CMD (/usr/local/bin/backup.sh)
# CROND: (root) CMDOUT (Permission denied)

# Check permissions
ls -la /usr/local/bin/backup.sh

# Fix -- make executable
sudo chmod +x /usr/local/bin/backup.sh

# Also check the script's shebang line
head -1 /usr/local/bin/backup.sh
# Must be: #!/bin/bash  or  #!/bin/sh  etc.
# Missing shebang = executed by /bin/sh which may not understand bash syntax

# Check scripts in cron.daily/weekly are also executable
ls -la /etc/cron.daily/
sudo chmod +x /etc/cron.daily/mybackup
Scripts in /etc/cron.daily/ must be executable and must not have a file extension (no .sh). run-parts — which executes these directories — skips files with extensions by default on many systems.
4
Common Failure: PATH and Command Not Found
# Symptom: script runs fine manually, silently does nothing in cron
# Root cause: command not in cron's minimal PATH

# Find where a command actually lives
which python3
which node
which aws
which myapp

# Fix 1: Use full paths in crontab
# WRONG:
0 2 * * * backup.sh
# RIGHT:
0 2 * * * /usr/local/bin/backup.sh

# Fix 2: Set PATH at top of crontab
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/myapp/bin
0 2 * * * backup.sh

# Fix 3: Use full paths inside the script
# Change: python3 script.py
# To:     /usr/bin/python3 /usr/local/bin/script.py

# Fix 4: Source profile inside the script
# #!/bin/bash
# source /etc/profile
# source ~/.bash_profile 2>/dev/null || true
The definitive fix: full paths everywhere. In cron jobs and the scripts they call, always use full absolute paths for every command. /usr/bin/python3 not python3. /usr/local/bin/backup.sh not backup.sh. This eliminates PATH issues entirely.
5
Common Failure: Percent Signs and Special Characters
# In crontab, % is special -- it means newline
# This BREAKS silently:
0 2 * * * /usr/bin/find /tmp -name "*.log" -mtime +7 -exec rm {} \;
# The backslash before ; is fine, but % in date format breaks:
0 2 * * * echo $(date +%Y-%m-%d) > /var/log/date.txt  # BROKEN

# Fix: escape % with backslash
0 2 * * * echo $(date +\%Y-\%m-\%d) > /var/log/date.txt  # OK

# Better fix: put the date command in the script, not the crontab
0 2 * * * /usr/local/bin/log-date.sh

# Also watch out for: quotes, $(), and other shell expansion
# When in doubt, put complex commands in a script
⚠️ The percent sign rule catches everyone. In crontab syntax, an unescaped % is treated as a newline. date +%Y-%m-%d in a crontab becomes date + followed by a newline and the rest as stdin. Always escape with \% or move complex commands into a script.
6
Common Failure: Relative Paths and Working Directory
# Cron sets HOME but working directory is typically /
# Relative paths in scripts fail unpredictably

# WRONG -- relative path assumes current directory
0 2 * * * ./backup.sh

# WRONG -- script tries to open config.txt from /
# #!/bin/bash
# cat config.txt  # fails -- not in /

# FIX -- cd to script directory at start of script
# #!/bin/bash
# cd "$(dirname "$0")" || exit 1
# cat config.txt  # now works -- in same dir as script

# Or set explicit working directory in crontab
0 2 * * * cd /usr/local/myapp && ./run.sh

# Check HOME and LOGNAME in cron
* * * * * echo "HOME=$HOME PWD=$PWD USER=$USER" >> /tmp/cron-dirs.txt
💡 Add cd "$(dirname "$0")" || exit 1 as the second line of every cron script. It changes to the script's own directory before doing anything, making relative paths work correctly regardless of where cron starts.
7
Adding Logging to Any Cron Job

A cron job with no logging is a black box. Add logging to every job you care about:

# Redirect both stdout and stderr to a log file
0 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1

# Add timestamps to log output
0 2 * * * echo "=== $(date) ===" >> /var/log/backup.log; \
           /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1

# Log to syslog using logger
0 2 * * * /usr/local/bin/backup.sh 2>&1 | /usr/bin/logger -t backup-cron

# Rotate log files with logrotate -- /etc/logrotate.d/backup-cron
sudo tee /etc/logrotate.d/backup-cron << 'EOF'
/var/log/backup.log {
    daily
    rotate 14
    compress
    missingok
    notifempty
    create 0644 root root
}
EOF

# Monitor log for errors
tail -f /var/log/backup.log
grep -i "error\|fail\|warn" /var/log/backup.log | tail -20
8
Cron Troubleshooting Checklist Script
#!/bin/bash
# cron-check.sh -- quick diagnostic for cron problems

echo "========================================"
echo " Cron Diagnostic -- $(hostname)"
echo " $(date)"
echo "========================================"

echo ""
echo "--- CRON DAEMON STATUS ---"
systemctl is-active crond 2>/dev/null || systemctl is-active cron 2>/dev/null
systemctl status crond 2>/dev/null | grep -E "Active|since" || \
systemctl status cron  2>/dev/null | grep -E "Active|since"

echo ""
echo "--- ROOT CRONTAB ---"
crontab -l 2>/dev/null || echo "(no root crontab)"

echo ""
echo "--- /etc/cron.d/ FILES ---"
ls -la /etc/cron.d/ 2>/dev/null

echo ""
echo "--- RECENT CRON LOG ENTRIES ---"
journalctl -u crond --since "1 hour ago" --no-pager 2>/dev/null | tail -20 || \
grep CRON /var/log/syslog 2>/dev/null | tail -20 || \
tail -20 /var/log/cron 2>/dev/null

echo ""
echo "--- CRON.DAILY SCRIPTS ---"
ls -la /etc/cron.daily/

echo ""
echo "--- CRON.WEEKLY SCRIPTS ---"
ls -la /etc/cron.weekly/

echo "========================================"
Run this script first whenever a cron job is reported as not working. It gives you daemon status, what jobs are defined, and recent log activity — the three things that answer 90% of cron questions in under 30 seconds.

Quick Reference

Cron Failure Patterns and Fixes

SymptomLikely CauseFix
No CMD in cron logWrong syntax, wrong crontab, crond not runningCheck crontab -l, systemctl status crond, verify syntax
CMD logged but nothing happensScript fails silently, PATH issue, permission deniedAdd logging, use full paths, check chmod +x
Command not foundCommand not in cron's PATHUse full path or set PATH= at top of crontab
Permission deniedScript not executable or not readable by cron userchmod +x script, check file ownership
Works manually, fails in cronEnvironment difference, PATH, or missing sourced filesTest with minimal env, use full paths, source profile in script
% causes weird behaviorUnescaped percent sign in crontabEscape as \% or move command to script
File not found in scriptRelative path, wrong working directoryUse absolute paths, add cd "$(dirname "$0")"
Script in cron.daily not runningHas .sh extension, not executable, or wrong permissionsRemove extension, chmod +x, check run-parts behavior
cron.d job not runningFile is world-writable or not owned by rootchmod 644, chown root:root
Job overlaps with itselfPrevious run still going when next firesAdd lockfile with flock or pidfile check

Essential Diagnostic Commands

CommandWhat it checks
systemctl status crondIs cron daemon running?
crontab -lWhat jobs are scheduled for current user?
crontab -l -u aliceWhat jobs for a specific user?
journalctl -u crond --since todayToday's cron activity
grep CRON /var/log/syslogCron log on Debian/Ubuntu
tail -f /var/log/cronFollow cron log on RHEL
ls -la /etc/cron.d/System drop-in jobs and permissions
ls -la /etc/cron.daily/Daily scripts and executability
sudo -u alice env -i /bin/sh script.shRun as cron user with minimal env
* * * * * env > /tmp/cron-env.txtCapture cron's actual environment