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.

4 min read Seiri Team cron job not runningcron not executingcron didn't runcron 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 | head

Check:

  • 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.d files 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.sh

Cron 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.sh

Missing 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/null

7. 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

SymptomLikely fix
Empty crontab -lRestore from config management / backup; do not hand-edit only on one host
Daemon inactivesystemctl enable --now cron (or crond); fix image if containers
Permission deniedchmod +x, correct owner, SELinux/AppArmor context if applicable
Works manually, not under cronAbsolute paths + env file sourced from the script
/etc/cron.d ignoredFilename 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_REF

Nightly 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/fail

Create 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.app

Details: 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

  1. Store crontabs in Ansible/Puppet/cloud-init — drift is how lines disappear.
  2. Prefer /etc/cron.d managed files over ad-hoc crontab -e on cattle hosts.
  3. One Seiri monitor per critical schedule; optional host-level pulse.
  4. Document the expected user and timezone next to the job.
  5. After every OS migrate or image bump, confirm a check-in lands.

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
Share X / Twitter LinkedIn