Why is my cron job not running?
A cron job that stops silently is the hardest failure to diagnose — because there's nothing to see. No error, no log entry, no email. Here are the seven most common causes, with the diagnostic command for each, and then the fix that makes sure you know immediately the next time it happens: one curl ping appended to your crontab line.
The 7 most common reasons a cron job stops running
Work through these in order — earlier ones are more common.
-
Cause 01
The cron daemon isn't running
Before anything else: is cron (or crond) actually running?
# systemd-based systems (Ubuntu 16+, Debian, RHEL 7+) systemctl status cron # or: systemctl status crond # SysV init / older systems service cron status # If stopped: sudo systemctl start cron && sudo systemctl enable cron -
Cause 02
Wrong timezone — the job runs at the wrong time or not at all
Cron uses the system timezone unless you set
CRON_TZat the top of the crontab. If your server is in UTC but you wrote the schedule thinking in local time (or vice versa), the job fires at an unexpected hour — or its window passes while you're watching the wrong clock.# Check the server's timezone timedatectl # Override per-job in crontab: CRON_TZ=America/New_York 0 3 * * * /usr/local/bin/backup.sh && curl -fsS -m 10 --retry 3 -o /dev/null https://ping.lastping.dev/<your-monitor-id> -
Cause 03
Minimal PATH — commands not found silently
Cron's default PATH is
/usr/bin:/bin. Any binary in/usr/local/bin,~/.nvm/versions/node/…/bin,~/.rbenv/shims, or a conda environment is invisible. The job fails immediately with "command not found" — which cron captures in the local mail spool and nowhere else.# Reproduce cron's environment locally: env -i HOME=$HOME /bin/sh -c 'your-command' # Fix 1: use full absolute paths 0 3 * * * /usr/local/bin/python3 /home/deploy/backup.py # Fix 2: set PATH at the top of crontab PATH=/usr/local/bin:/usr/bin:/bin 0 3 * * * backup.py # Fix 3: source your profile (slower, but catches everything) 0 3 * * * /bin/bash -l -c '/usr/local/bin/backup.sh' -
Cause 04
Syntax error in the crontab
A typo in the schedule expression — five fields, not four or six; a
*/where only a number works; a tab/space issue — means the job is registered but never scheduled. Cron usually logs a parse error to syslog but not always visibly.# List your crontab — look for parse errors on load crontab -l # Validate online: https://crontab.guru # Example: "0 3 * * *" = 03:00 every day # Common mistake: "0 3 * * * *" — 6 fields, not 5; the 6th is treated as command -
Cause 05
Script exits non-zero but nobody is listening
If your script returns a non-zero exit code, cron sends output to
MAILTO— typically a local spool file that nobody reads. The job "ran" from cron's perspective; the failure was inside the script and went nowhere useful.# Detect in cron logs grep CRON /var/log/syslog | grep "(backup.sh)" # Add immediate failure alerting 0 3 * * * /usr/local/bin/backup.sh \ && curl -fsS -m 10 --retry 3 -o /dev/null https://ping.lastping.dev/<id> \ || curl -fsS -m 10 --retry 3 -o /dev/null https://ping.lastping.dev/<id>/fail -
Cause 06
Script not executable
A script deployed without executable permission runs fine via
bash script.shbut fails silently when cron tries to exec it directly. Check the permission bits.# Check ls -l /usr/local/bin/backup.sh # Fix chmod +x /usr/local/bin/backup.sh -
Cause 07
Wrong user's crontab
The crontab was installed under a different user — often root or a deploy user. Check all relevant accounts. Also check
/etc/cron.d/and/etc/crontab, which include a username column that regular crontabs don't.# Check root's crontab sudo crontab -l # Check system crontabs cat /etc/crontab ls /etc/cron.d/ # See all running cron jobs (requires root) for user in $(cut -f1 -d: /etc/passwd); do sudo crontab -u "$user" -l 2>/dev/null | grep -v '^#' | sed "s/^/$user: /" done
The permanent fix: dead-man's-switch detection
Diagnosing the current problem is necessary. Making sure you know immediately about the next one is better. One curl ping appended to the crontab line does both.
Create a heartbeat monitor in LastPing
In the console, create a monitor with the job's schedule and a grace window. Copy the ping URL.
Append the ping (success only) to your crontab
The && means the ping only fires if the job exits 0. A
failed or never-started job leaves silence — and LastPing opens an incident.
Optional: alert immediately on failure too
Add || curl …/fail to be paged the moment the job fails,
instead of waiting for the grace window to expire.
This approach catches all seven causes above: the daemon stopped (no ping), wrong timezone (ping arrives at the wrong time and triggers a late alert), script failure (ping skipped), and more. For the full copy-paste guide including hung-job detection and wrapper scripts, see Monitor cron jobs. Running on Kubernetes? See Kubernetes CronJobs. For the concept behind why absence is the reliable signal, see dead-man's-switch monitoring explained.
Questions people ask
Front-loaded answers — the most important fact first.
-
Why is my cron job not running?
The seven most common causes: (1) cron daemon stopped; (2) wrong timezone; (3) PATH is too short — commands not in
/usr/bin:/binare invisible; (4) crontab syntax error; (5) script exits non-zero and errors go to an unread mail spool; (6) script not executable; (7) job is in a different user's crontab. Work through the checklist above in order. -
How do I check if cron actually ran my job?
Check the system log:
grep CRON /var/log/syslog(Debian/Ubuntu) orgrep cron /var/log/cron(RHEL/CentOS). Each run leaves a line withCMD (command). No line at all means the schedule didn't fire. A line with no follow-up means the job started and the problem is inside the script. -
How do I test a cron job without waiting for the schedule?
Run it with cron's minimal environment:
env -i HOME=$HOME /bin/sh -c 'your-command'. Theenv -iclears all variables, reproducing exactly what cron sees. Any "command not found" errors will surface immediately, without needing to wait for the next schedule window. -
What is dead-man's-switch monitoring for cron?
Instead of the cron job reporting failures (which requires it to run), you set up an external expectation: "I expect a ping from this job every day at 03:00 UTC plus 30 minutes grace. If no ping arrives, open an incident." That expectation catches everything — daemon stopped, crontab lost, script hung, machine rebooted without the job — because none of those can report their own absence. LastPing is free, fully hosted, and catches all of them. See dead-man's-switch explained for the full concept.
-
Is monitoring free?
Yes. LastPing is free and fully hosted with no monitor cap and no paid tier. See how it compares to Healthchecks.io and Cronitor.
Fix the current problem. Prevent the next one.
One crontab edit puts detection in place permanently. You'll know the moment the next failure happens — not when a user reports it or a backup turns out to be missing.