Cron Job Debugging

Step-by-step guide to debugging cron jobs that do not run. Environment, paths, permissions, output capture, and log inspection techniques.

ZERO UPLOAD · ALL LOCAL
  1. Type or paste a 5-field cron expression into the input (e.g. */15 * * * *).
  2. Use the preset buttons to load a common schedule instantly.
  3. Edit individual fields (MIN, HR, DOM, MON, DOW) — the expression updates live.
  4. Read the SCHEDULE panel for a plain-English description of the pattern.
  5. Choose a timezone and clock format, then read NEXT 5 RUNS for upcoming execution times.

Worked examples for this use case

Job runs in terminal but not in cron

Before
0 3 * * * python3 /home/user/backup.py
After
SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin
MAILTO=""
0 3 * * * /usr/local/bin/python3 /home/user/backup.py >> /tmp/backup.log 2>&1

Absolute path, explicit SHELL and PATH, and output capture resolve the three most common failure modes.

Speed up the debug cycle

Before
0 3 * * * /usr/local/bin/python3 /home/user/backup.py >> /tmp/backup.log 2>&1
After
* * * * * /usr/local/bin/python3 /home/user/backup.py >> /tmp/backup.log 2>&1

Change to every minute while debugging, then restore the original schedule.

PRESETS

MIN
HR
DOM
MON
DOW
SCHEDULE

NEXT 5 RUNS

    Cron Job Debugging: Why Your Job Is Not Running

    When cron does not do what you expect, start with the facts it exposes. A job that misses its time, produces no output, or exits with an error leaves no obvious trace unless you set up logging explicitly. Debugging starts with ruling out the most common causes in a fixed order: syntax, environment, path, and permissions.

    Working from the outside in, the first question is whether the expression matches any time at all. The second is whether the command would succeed if run as the cron user in a minimal environment. Only after confirming both do you investigate the system log, because most cron job failures are path or environment issues, not scheduler problems. A disciplined order also prevents accidental schedule changes while chasing a command failure. It keeps the investigation focused and repeatable when the next outage is time-sensitive.

    Environment and path failures

    Cron provides a minimal environment: SHELL=/bin/sh, PATH=/usr/bin:/bin.1 A command like python3 myscript.py works in your terminal because your PATH includes /usr/local/bin, but cron cannot find python3 there. Use absolute paths (/usr/local/bin/python3 /home/user/myscript.py) or set PATH=/usr/local/bin:/usr/bin:/bin at the top of the crontab. Similarly, source ~/.bashrc and bash-specific syntax ([[ ]], (( ))) fail when SHELL is /bin/sh. The gap between your interactive shell and the cron environment is the single most common reason a job that works at the terminal silently produces nothing on the schedule.

    Checking permissions before logs

    Before inspecting system logs, confirm the cron user can actually execute the script. Run ls -la on the script file to verify the execute bit is set, and check that every parent directory grants execute permission to the cron user. A script inside a home directory with chmod 700 fails silently when cron runs as a different user, because the daemon cannot traverse the directory path to reach the file. Fixing permissions first eliminates an entire class of failures before you spend time searching logs for errors that never appear.

    Also confirm the script is executable with chmod +x and that the cron user owns or can read every file the command touches, because a job that fails on a permission error leaves no output for you to find later. The safest habit is to run the exact command as the cron user from a terminal before trusting the schedule, since that reproduces the minimal PATH and home directory that the daemon will actually use. Catching the denial locally beats waiting for the next missed run to surface it.

    Capturing output for debugging

    Add >> /tmp/cron-debug.log 2>&1 to the command to capture both stdout and stderr. Without this, output either goes to MAILTO (default: the system mail for the user) or is discarded. Set MAILTO="" to suppress email delivery and rely only on the log file. Building on this, temporarily changing the schedule to * * * * * (every minute) speeds up the debug loop: results appear in 1 minute instead of waiting for the actual schedule.

    The key principle is to never rely on the default cron output handling for debugging. Between the minimal PATH, the lack of a terminal, and the absence of interactive prompts, a cron job can fail in ways that leave no trace unless you explicitly redirect output to a file. Once redirection is in place, the debug log becomes the single source of truth for every run, and you can iterate on the command without waiting for the real schedule to fire.

    Reading system logs and checking permissions

    On systemd-based systems, journalctl -u cron or journalctl -u crond shows cron daemon messages, including job start/stop records and error output.2 On older systems, check /var/log/syslog or /var/log/cron.3 Permissions issues appear as "Permission denied" or "command not found" in these logs. The script must be executable (chmod +x script.sh), and the cron user must have read and execute access to both the script and its containing directory.

    Log entries are most useful when you correlate them with the expected schedule. If the daemon logged a job start at the expected time but your debug log shows nothing, the command ran but failed immediately, and the error is in the command rather than the schedule. Conversely, if no start line appears at all, the daemon never attempted the job, and the problem lies in the expression or the crontab file itself.

    Simulating the cron environment in a terminal

    Before waiting for the scheduled time to arrive, confirm your command works in a cron-like environment in your terminal. The env -i command strips the current environment and starts fresh: env -i HOME=/home/youruser SHELL=/bin/sh PATH=/usr/bin:/bin /bin/sh -c 'your command'. If the command fails here, it also fails in cron.4 Fix the missing path, missing variable, or syntax issue before adding the entry. Replicating the cron environment locally is the fastest way to distinguish a command problem from a scheduling problem before the job ever reaches production.

    Testing as the exact cron user

    For jobs that run as a service user rather than your own account, use sudo -u www-data env -i HOME=/var/www SHELL=/bin/sh PATH=/usr/bin:/bin /bin/sh -c 'your command' to reproduce that user's cron environment exactly. Service users often lack home directories, write permissions, or executables in their PATH that you take for granted in your own account. Testing as the exact user before deploying prevents permission denied errors that only appear after the cron daemon fires the job.

    Reading the cron system log for a specific job

    On systemd distributions, use journalctl for the cron unit and filter the output to recent activity. Each line shows the PID of the subprocess the daemon spawned, the username, and the command. If your job does not appear in these lines during the expected window, the daemon did not attempt to run it: the expression may not match the current time, or the crontab may contain a parse error that prevents the daemon from reading the entry.

    Separating schedule misses from command failures

    When the job appears in the system log as started but your log file shows no output, the command ran but produced nothing, or output redirection is missing. Add >> /tmp/cron-debug.log 2>&1 and set the schedule to * * * * *; check the debug log after one minute. Both stdout and stderr now appear in a single location. If the system log shows no start line at all during the expected window, the daemon never attempted the job, and the problem is in the expression rather than the command.

    When to use this

    Use this guide when a cron job stops running, runs but produces no output, runs on the wrong system but not on production, or was recently working and stopped after a system change or permission update. Before chasing environment and permission issues, see whether the cron trigger is even the problem, comparing the listed run times against what you expected.

    Examples

    Job runs in terminal but not in cron

    Before
    0 3 * * * python3 /home/user/backup.py
    After
    SHELL=/bin/bash
    PATH=/usr/local/bin:/usr/bin:/bin
    MAILTO=""
    0 3 * * * /usr/local/bin/python3 /home/user/backup.py >> /tmp/backup.log 2>&1

    Absolute path, explicit SHELL and PATH, and output capture resolve the three most common failure modes.

    Speed up the debug cycle

    Before
    0 3 * * * /usr/local/bin/python3 /home/user/backup.py >> /tmp/backup.log 2>&1
    After
    * * * * * /usr/local/bin/python3 /home/user/backup.py >> /tmp/backup.log 2>&1

    Change to every minute while debugging, then restore the original schedule.

    Sources
    1. 1.

      Linux man7, "crontab(5) — Linux manual page," man7.org, accessed June 2026. https://www.man7.org/linux/man-pages/man5/crontab.5.html

    2. 2.

      Free Desktop, "journalctl," freedesktop.org, accessed June 2026. https://freedesktop.org/software/systemd/man/latest/journalctl.html

    3. 3.

      Linux man7, "cron(8) — Linux manual page," man7.org, accessed June 2026. https://www.man7.org/linux/man-pages/man8/cron.8.html

    4. 4.

      Cronitor, "How to Simulate the Environment Cron Executes a Script," cronitor.io, accessed June 2026. https://cronitor.io/guides/how-to-simulate-the-environment-cron-executes-a-script

    FAQ