"It works when I run it manually" — the classic symptom
The most common cron debugging scenario: a script runs perfectly when executed directly in a terminal, but fails or does nothing when triggered by cron. The reason is almost always environment: cron runs jobs with a minimal environment — a bare PATH, no shell profile (.bashrc/.profile) sourced, and no inherited variables from your interactive session. A script that relies on a PATH-dependent binary, an environment variable set in your shell config, or a relative file path assuming a specific working directory can behave completely differently under cron's stripped-down environment.
Where output actually goes
By default, cron emails any output (stdout/stderr) to the crontab owner's local mail — which on most modern servers is never actually read, since local mail delivery isn't configured. This makes jobs look "silent" even when they're producing errors constantly. Explicitly redirecting output fixes this:
*/5 * * * * /path/to/script.sh >> /var/log/myjob.log 2>&1
This appends both stdout and stderr to a log file you can actually check — the single highest-value change for debugging any cron job that isn't behaving as expected.
Checking whether the job ran at all
Most systems log cron's own activity separately from the job's output — typically /var/log/cron or accessible via journalctl -u cron (or crond) on systemd-based systems. This tells you whether cron even attempted to trigger the job, which narrows the problem: if cron never triggered it, the issue is the expression or the crontab itself; if it triggered but the job failed, the issue is inside the script or its environment.
Permission and path issues
- Script not executable. A
.shfile missing the executable bit (chmod +x) fails silently under cron even if it runs fine when invoked directly withbash script.sh. - Relative paths breaking. Cron's working directory is not your terminal's — always use absolute paths for scripts, config files, and any file the job reads or writes.
- User permission mismatches. A job in the wrong user's crontab (or running as a different user via
/etc/cron.d) may lack permissions the same command has when you run it as yourself.
Common mistakes
- Debugging only by staring at the crontab. The expression is rarely the actual bug once it's confirmed to trigger — environment and paths are the more common culprits.
- Not redirecting output anywhere checkable. Unread local mail is the default destination and is effectively the same as no logging at all.
- Assuming
PATHis inherited from your shell. Cron'sPATHis minimal; hardcode full binary paths (/usr/bin/python3instead ofpython3) in cron-triggered scripts.
FAQ
Why does my script work manually but not under cron?
Almost always environment differences — cron provides a minimal PATH and no shell profile, so scripts relying on inherited variables or relative paths behave differently.
Where does cron's output go if I don't redirect it?
To local mail for the crontab's owner by default — which is frequently never checked on modern servers, making failures look silent.
How do I check if cron actually attempted to run my job?
Check the system's cron log (/var/log/cron or journalctl -u cron/crond), separate from your job's own output logging.
Confirm your expression fires when you expect with the Cron Expression Descriptor before chasing environment-level bugs.