← Blog  ·  October 2026

Cron Job Not Running? A Practical Debug Guide

The job is in your crontab. The time has passed. Nothing happened. Here's how to find out why.

You added a cron job, waited for it to fire, and nothing happened. No output, no error, no log entry — just silence. This is one of the most frustrating debugging situations in Linux because cron gives you almost no feedback by default.

The good news: there are a fixed number of things that can cause a cron job to not run, and you can check each one in about ten minutes. This guide walks through them in order of how often they come up.

Most common causes — in order of frequency:

1 Check that cron is actually running

Before debugging the job itself, verify the scheduler is alive:

# Linux (systemd)
systemctl status cron       # Ubuntu/Debian
systemctl status crond      # RHEL/CentOS/Amazon Linux

# Linux (non-systemd)
service cron status

# macOS
launchctl list | grep cron

If it's stopped:

sudo systemctl start cron       # Ubuntu/Debian
sudo systemctl start crond      # RHEL/CentOS

On managed cloud platforms (Heroku Scheduler, GitHub Actions, AWS EventBridge) this isn't applicable — the scheduler is outside your control. Jump to step 3.


2 Check cron logs for your job

Cron logs tell you whether the daemon even attempted to run your job:

# Ubuntu/Debian (journald)
journalctl -u cron --since "1 hour ago"

# Ubuntu/Debian (syslog)
grep CRON /var/log/syslog | tail -50

# RHEL/CentOS
grep cron /var/log/cron | tail -50

# macOS
log show --predicate 'subsystem == "com.apple.xpc.launchd"' --last 1h | grep cron

What you're looking for:


3 Validate your crontab syntax

A single syntax error in crontab -e can silently disable the entire crontab on some distributions. Always edit with crontab -e rather than editing the file directly — it validates before saving.

# View your current crontab
crontab -l

# Edit safely (validates before saving)
crontab -e

Common syntax mistakes:

# Wrong: missing fields
*/5 * * /path/to/script.sh          # only 3 fields before command — needs 5

# Wrong: space inside a field
0  9  * * 1-5 /path/to/script.sh   # double space — usually fine, but check

# Wrong: percent sign in command (unescaped)
0 9 * * * echo "today: $(date +%Y-%m-%d)"   # %Y, %m, %d — percent signs must be escaped as \%
0 9 * * * echo "today: $(date +\%Y-\%m-\%d)"   # correct
The % gotcha: In crontab, an unescaped % is treated as a newline. Commands using date +%Y-%m-%d, Python format strings, or printf with %s will silently fail or produce unexpected output unless every % is escaped as \%.

Use an expression parser if you're unsure about the schedule fields: validate your cron expression here to see the plain-English schedule and next run times.


4 Fix the PATH problem

This is the most common cause of "runs fine from terminal, does nothing in cron." Cron runs with a minimal PATH:

/usr/bin:/bin

Your interactive shell has a much richer PATH from .bashrc, .profile, or .zshrc. Commands like node, python3, npm, php, composer, aws, or anything installed to /usr/local/bin or /home/user/.local/bin are not found.

Diagnose it

Add a PATH-dump job temporarily:

* * * * * env > /tmp/cron-env.txt

Wait a minute, then check what PATH cron actually has:

grep PATH /tmp/cron-env.txt

Fix it — three options

Option 1: Set PATH at the top of your crontab (applies to all jobs in the file):

PATH=/usr/local/bin:/usr/bin:/bin:/home/youruser/.local/bin
0 9 * * * /path/to/script.sh

Option 2: Use absolute paths in your script:

# Instead of:
node index.js

# Use:
/usr/local/bin/node /home/youruser/myapp/index.js

Option 3: Source your profile inside the script:

#!/bin/bash
source /home/youruser/.bashrc
# rest of your script

5 Check script permissions and shebang

Two quick checks:

# Is the script executable?
ls -la /path/to/script.sh

# Fix if not:
chmod +x /path/to/script.sh

Also verify the shebang line is correct and points to an existing interpreter:

head -1 /path/to/script.sh
# Should be: #!/bin/bash  or  #!/usr/bin/env python3  etc.

# Verify the interpreter exists:
which bash
which python3
Windows line endings: If the script was edited on Windows, it may have \r\n line endings. The shebang line #!/bin/bash\r will fail with bad interpreter. Fix with: sed -i 's/\r$//' script.sh

6 Check the working directory and relative paths

Cron sets the working directory to your home directory (~), not the directory where your script lives. Any relative path inside the script — ./config.json, ../data/, logs/app.log — will resolve relative to home, not relative to the script.

The fix: cd to your project directory at the top of your script:

#!/bin/bash
set -e
cd /home/youruser/myapp    # absolute path to your project

# Now relative paths work as expected
./run.sh
python3 process.py
node dist/index.js

Or do it inline in the crontab:

0 9 * * * cd /home/youruser/myapp && ./run.sh

7 Check environment variables

Cron doesn't load your .bashrc, .profile, or any shell startup file. Environment variables your script depends on — DATABASE_URL, API_KEY, HOME, LANG — won't be set unless you set them explicitly.

Option 1: Set them in the crontab file:

DATABASE_URL=postgres://user:pass@localhost/mydb
API_KEY=sk-abc123
0 9 * * * /path/to/script.sh

Option 2: Load a .env file inside your script:

#!/bin/bash
set -a
source /home/youruser/myapp/.env
set +a
# rest of your script

Option 3: Use a systemd timer instead of cron — systemd's EnvironmentFile= directive gives you a clean way to inject env vars into scheduled jobs:

# /etc/systemd/system/myjob.service
[Service]
Type=oneshot
EnvironmentFile=/home/youruser/myapp/.env
ExecStart=/home/youruser/myapp/script.sh

8 Confirm you're editing the right crontab

Cron has separate crontab files per user. If your job needs to run as root (to access certain files or ports), it won't appear in crontab -l as a regular user:

# Your own crontab
crontab -l

# Root's crontab
sudo crontab -l

# Another user's crontab (as root)
sudo crontab -l -u www-data

Also check system-wide cron directories — jobs placed here by package managers or sysadmins run on their own schedules:

ls /etc/cron.d/
ls /etc/cron.daily/
ls /etc/cron.hourly/

Quick diagnostic checklist

If you're not sure where to start, run through this in order:

  1. Run the script manually as the same user cron would use: sudo -u cronuser /path/to/script.sh. Does it succeed?
  2. Check the cron log for an attempt entry at the expected time.
  3. Add * * * * * env > /tmp/cron-env.txt temporarily — confirm PATH and env vars.
  4. Run the script with the cron-like environment: env -i HOME=/home/youruser PATH=/usr/bin:/bin /path/to/script.sh
  5. Redirect output to catch hidden errors: 0 9 * * * /path/to/script.sh >> /tmp/myjob.log 2>&1
  6. Verify the cron daemon is running and no reload errors are in the log.

Step 5 — redirecting both stdout and stderr to a log file — is the single most useful debugging tool. If cron is attempting your job but the job is failing silently, that log file will show you exactly what went wrong.


Once you've fixed it — don't rely on manual checking again

Debugging a failed cron job after the fact is slow and stressful. The better long-term solution is monitoring that alerts you the moment a job misses its window, before a user or customer notices first.

📟 CronWatch monitors your jobs and alerts you instantly

Once your job is running correctly, add a dead man's switch so you'll know the moment it stops — without grepping logs or waiting for a complaint. CronWatch monitors crontab, GitHub Actions scheduled workflows, Heroku Scheduler, and AWS EventBridge from a single dashboard.

Join the CronWatch waitlist → Validate your cron expression

Not sure whether your schedule expression is correct in the first place? Use the free cron expression parser to see the plain-English description and the next five run times before you deploy the job.