Cron Troubleshooting: Why Your Jobs Aren't Running

Cron jobs failing silently is frustrating and common. This systematic troubleshooting guide walks through the most frequent problems and their solutions, helping you get your scheduled jobs running reliably.

Verify the Basics First

Before diving into complex debugging, verify the fundamentals. Many cron issues stem from simple oversights that are easy to check.

First, confirm your crontab was actually saved. After editing with "crontab -e", verify it with "crontab -l" to list your crontab. If your entries don't appear, they weren't saved. Common causes: editor closed without saving, wrong user account (crontab is per-user), or syntax errors causing the crontab to be rejected.

Check that the cron daemon is running. On most Linux systems, use "systemctl status cron" or "systemctl status crond" (the daemon name varies by distribution). If cron isn't running, your jobs can't execute. Start it with "systemctl start cron" or "service cron start".

Verify your cron syntax is valid. Use our crontab generator to validate your expression and see when it would run. A simple typo like "0 0 * * 8" (there's no day 8) or "60 0 * * *" (minute 60 doesn't exist) causes silent failures. The cron daemon often doesn't report syntax errors clearly.

Check system logs for cron execution. On systemd systems, use "journalctl -u cron" or "journalctl -u crond". On older systems, check /var/log/syslog or /var/log/cron. These logs show when cron starts jobs, even if the job itself fails. If you don't see your job starting, the problem is with cron scheduling. If you see it starting but failing, the problem is with your command.

Confirm you're editing the right user's crontab. Cron jobs run as the user who owns the crontab. If you need root permissions, use "sudo crontab -e" to edit root's crontab, not your user's crontab. Running "crontab -e" as a regular user then trying to run commands requiring root privileges will fail with permission errors.

Ensure your command exists and is executable. From a terminal, run the exact command from your crontab manually. If it fails interactively, it will definitely fail in cron. If it succeeds interactively but fails in cron, the problem is likely environment differences (covered next).

Environment and Path Issues

The most common reason cron jobs fail is environment differences between your interactive shell and cron's minimal environment. When you run commands from your terminal, your shell provides extensive setup: PATH with locations to find executables, environment variables from .bashrc or .profile, working directory set to your home or project directory, and language/locale settings. Cron provides almost none of this.

Cron's PATH is typically just "/usr/bin:/bin", meaning it can only find executables in those two directories. If your script is in /home/user/bin, cron won't find it. If your job uses "python" and python is in /usr/local/bin or a virtualenv, cron can't find it. Always use absolute paths: "/usr/bin/python3" instead of "python3", "/home/user/scripts/backup.sh" instead of "backup.sh".

Environment variables you rely on don't exist in cron. DATABASE_URL, API_KEYS, AWS credentials, and other variables from your .bashrc aren't available. Set them explicitly in the crontab itself (at the top, before any job lines: "DATABASE_URL=..." or in your script. Alternatively, source your environment file: ". /home/user/.env && /home/user/script.sh".

The working directory is unpredictable in cron—often the user's home directory, but not guaranteed. If your script does "open('config.json')" expecting a local file, it will fail when run from a different directory. Use absolute paths for all file operations, or explicitly cd to the needed directory: "cd /home/user/project && ./script.sh".

To debug environment issues, create a test cron job that dumps the environment: "* * * * * env > /tmp/cron-env.txt". Run it, wait a minute, then examine /tmp/cron-env.txt to see exactly what environment cron provides. Compare this to running "env" in your interactive shell to see what's missing.

For Python projects, activate virtualenvs explicitly: "*/5 * * * * cd /path/to/project && /path/to/venv/bin/python script.py". The virtualenv's Python binary includes the environment setup. Similarly for Node.js, Ruby, or other language-specific environments.

Locale issues cause subtle bugs. Cron might run with "POSIX" locale while your development environment uses "en_US.UTF-8". This affects string sorting, date parsing, and character encoding. Set locale explicitly if needed: "LC_ALL=en_US.UTF-8 /path/to/script".

Consider creating a wrapper script that sets up the environment consistently, then calls your actual script. The wrapper sources environment files, sets PATH, cd to the right directory, and handles common setup. This centralizes environment configuration rather than repeating it in every crontab entry.

Logging and Output

Cron job failures are hard to debug without output. By default, cron emails command output and errors to the user, but most modern systems don't have local mail configured, so this output vanishes. Explicit logging solves this.

Redirect stdout and stderr to a log file: "0 2 * * * /path/to/script.sh > /var/log/myjob.log 2>&1". The "> /var/log/myjob.log" redirects standard output (stdout) to the file. The "2>&1" redirects standard error (stderr) to wherever stdout is going (the log file). Now all output, including error messages, goes to a file you can examine.

Append instead of overwrite to build a history: "0 2 * * * /path/to/script.sh >> /var/log/myjob.log 2>&1". The ">>" appends to the file instead of replacing it. Each run adds to the log, creating a history. Be mindful of log file growth—implement log rotation or periodic cleanup.

Add timestamps to distinguish runs: In your script, use "date" to print when it runs. Or use a logging library that automatically timestamps entries. Without timestamps, it's hard to tell which log entries came from which execution.

Log rotation prevents unbounded growth. Use logrotate to automatically archive, compress, and delete old logs. Create /etc/logrotate.d/myjob with configuration to rotate your cron job logs weekly or when they reach a certain size.

For critical jobs, consider both logging to file and alerting on failure. Capture output to a log, then check the exit code: "0 2 * * * /path/to/script.sh >> /var/log/myjob.log 2>&1 || echo 'Job failed' | mail -s 'Cron Failure' [email protected]". The "||" means "or"—if the script fails (non-zero exit), send an email.

Separate stdout and stderr if you need to distinguish normal output from errors: "0 2 * * * /path/to/script.sh >> /var/log/myjob.out 2>> /var/log/myjob.err". Now normal output goes to .out and errors go to .err.

For testing, use a short interval (every 2 minutes) and check logs frequently: "*/2 * * * * /path/to/script.sh >> /tmp/test.log 2>&1". Once working, change to the production schedule and move logs to the proper location.

Remember that without redirection, cron tries to email output. If your system generates email locally (check /var/mail/username), you might find output there. But explicit logging is more reliable than depending on local mail.

Try the Tool

Crontab Generator

Crontab Generator

Related Articles