Blog 2026-07-24
Cron Job Not Running: Diagnose and Alert When Cron Stops
Troubleshoot cron not executing, cron didn't run, and missed schedules — plus how to get alerted automatically next time with heartbeat monitoring.
Quick answer: If a cron job is not running, verify the crontab entry (correct user), that cron/crond is active, and that logs show a CMD at the expected time. Then add a heartbeat so the next miss pages you instead of waiting for a human to notice.
Searches like cron job not running, cron not executing, and cron didn’t run usually mean the same incident: expected work never started. That is different from “ran but failed quietly” (cron job failed silently), though both need the same long-term fix — push check-ins, not uptime polls.
Seiri asks Did it run? via heartbeat / dead man’s switch monitoring — not APM, not “is port 443 up?”
Diagnosis checklist
1. Confirm the crontab you think is live
crontab -l
sudo crontab -l
sudo ls /etc/cron.d /etc/cron.daily /etc/cron.hourly
sudo grep -R "" /etc/cron.d 2>/dev/null | headCheck:
- Schedule fields (minute hour dom month dow) —
0 2 * * *is 02:00, not “every 2 hours.” %in commands — cron treats%specially; escape as\%or move logic into a script.- User —
/etc/cron.dfiles need a user column; user crontabs do not.
2. Confirm the daemon is running
systemctl status cron 2>/dev/null || systemctl status crond 2>/dev/null
ps aux | grep -E '[c]ron'Container images often ship without cron. If the app expects schedule:run every minute (Laravel), missing cron inside the container is a common “not running” root cause — see Laravel scheduler monitoring.
3. Read execution logs
grep CRON /var/log/syslog | tail -50
journalctl -u cron -n 50 --no-pager
journalctl -u crond -n 50 --no-pager- No CMD lines for your job → not scheduled or daemon/config issue.
- CMD lines present → cron started the process; debug the script (permissions, shebang, env).
4. Permissions, shebang, absolute paths
ls -l /usr/local/bin/backup.sh
head -1 /usr/local/bin/backup.sh # #!/usr/bin/env bash
sudo -u deploy /usr/local/bin/backup.shCron does not load your shell profile. Use absolute paths to php, python, node, and scripts.
5. Environment and secrets
# Compare interactive vs cron-like env
env -i HOME=/home/deploy USER=deploy PATH=/usr/bin:/bin \
/usr/local/bin/backup.shMissing AWS_*, DATABASE_URL, or wrong HOME often presents as “cron didn’t run” when it actually ran and exited immediately.
6. Daylight saving / timezone
CRON_TZ= (when supported) and system timezone changes shift wall-clock expectations. Confirm:
timedatectl
grep CRON_TZ /etc/crontab /var/spool/cron/* 2>/dev/null7. Not classic cron?
- systemd timers:
systemctl list-timers --all - Kubernetes CronJob:
kubectl get cronjobs,jobs— see Kubernetes CronJob monitoring - Platform schedulers: GitHub Actions
schedule, cloud scheduler jobs — different control plane, same “did it fire?” question
Fix the immediate break
| Symptom | Likely fix |
|---|---|
Empty crontab -l | Restore from config management / backup; do not hand-edit only on one host |
| Daemon inactive | systemctl enable --now cron (or crond); fix image if containers |
| Permission denied | chmod +x, correct owner, SELinux/AppArmor context if applicable |
| Works manually, not under cron | Absolute paths + env file sourced from the script |
/etc/cron.d ignored | Filename without dot issues; must be owned by root; valid user field |
After repair, force a near-term schedule (e.g. next minute) and watch logs once before walking away.
Monitoring fix: alert when cron stops
Diagnosis after the fact is not a strategy. Add a Seiri heartbeat so silence is an alert.
# Every 5 minutes — pulse that cron + wrapper are alive
*/5 * * * * /app/worker-health.sh && \
curl -fsS -m 10 --retry 3 \
https://ping.seiri.app/webhook/YOUR_ORG:YOUR_REFNightly job (business signal):
0 2 * * * /usr/local/bin/backup.sh && \
curl -fsS -m 10 --retry 3 \
https://ping.seiri.app/webhook/YOUR_ORG:YOUR_REF/success \
|| curl -fsS -m 10 \
https://ping.seiri.app/webhook/YOUR_ORG:YOUR_REF/failCreate monitors at cloud.seiri.app (free forever). Set schedule + grace to match reality.
No outbound HTTPS:
0 2 * * * /usr/local/bin/backup.sh && \
echo "ok $(date -u +%FT%TZ)" | mail -s backup YOUR_REF@ping.seiri.appDetails: email heartbeat monitoring.
Layering: a 5-minute pulse catches “cron daemon / crontab gone.” A per-job success ping catches “this backup never finished.” Use both for hosts that carry critical schedules — same idea as scheduled job monitoring.
Kubernetes: ping from the Job container, or use seiri-kube-agent (product).
Hardening so it stays running
- Store crontabs in Ansible/Puppet/cloud-init — drift is how lines disappear.
- Prefer
/etc/cron.dmanaged files over ad-hoccrontab -eon cattle hosts. - One Seiri monitor per critical schedule; optional host-level pulse.
- Document the expected user and timezone next to the job.
- After every OS migrate or image bump, confirm a check-in lands.
Related
Transform Your Monitoring
Get started with Seiri
Heartbeat monitoring for cron jobs, workers, and Kubernetes — know when scheduled work goes silent.
Start free- Free forever
- No credit card required
- Cancel anytime
Would you know if this job stopped running?
Seiri watches your cron jobs, workers, and Kubernetes CronJobs, and alerts you the moment one goes silent. Free forever, no credit card.
Start free