DEV Community

Maruthi Vishnu Vardhan Reddy Rachamallu
Maruthi Vishnu Vardhan Reddy Rachamallu

Posted on Originally published at sysadmin.co.in

Cron Job Not Running? A Checklist to Run Before Blaming cron

The job was supposed to run at 02:15. It’s 09:40. Finance wants the report. Cron has strong opinions and a tiny environment. Most “cron isn’t working” tickets are PATH, permissions, wrong user, or the daemon quietly not running.

Here’s the checklist I use before blaming the scheduler gods. Keep chai nearby — this is usually a 15-minute mystery once you’re methodical.

Automation done well is a bridge into DevOps habits — after you fix today’s crontab, skim the SysAdmin → DevOps roadmap for moving critical jobs into reviewed, logged pipelines.

1) Is cron even running?

systemctl status cron          # Debian/Ubuntu
systemctl status crond         # RHEL-family
ps aux | grep -E '[c]ron'
Enter fullscreen mode Exit fullscreen mode

Terminal showing systemctl is-enabled and is-active for cron both positive
Two quick checks: is the cron service enabled at boot, and is it running right now? Real output from an Ubuntu 24.04 server.

If the unit failed, fix that first with normal systemd + journalctl technique.

2) Which crontab did you edit?

  • crontab -e — current user’s crontab
  • sudo crontab -e — root’s crontab
  • /etc/crontab and /etc/cron.d/* — system files (they have a user field)
  • /etc/cron.daily etc. — run-parts scripts (must be executable, no dots in some distros’ run-parts rules)
crontab -l
sudo crontab -l
ls -la /etc/cron.d/ /etc/cron.daily/
Enter fullscreen mode Exit fullscreen mode

Editing the wrong file is embarrassingly common. No shame — just check.

3) Timing syntax and timezones

# m h dom mon dow command
15 2 * * * /usr/local/bin/backup.sh
Enter fullscreen mode Exit fullscreen mode

Confirm the server timezone:

timedatectl
date
Enter fullscreen mode Exit fullscreen mode

UTC vs Asia/Kolkata has ruined more “it didn’t fire” threads than bad asterisks. Containers may differ from the host.

4) Environment: PATH and shell

Cron gives you a minimal environment. That fancy script that works interactively fails because python isn’t on cron PATH.

Rules that prevent pain:

  • Absolute paths to binaries and scripts
  • Set PATH= at the top of the crontab if needed
  • Source a known env file explicitly inside the script
  • Don’t rely on aliases or interactive shell functions
15 2 * * * /usr/bin/flock -n /tmp/backup.lock /usr/local/bin/backup.sh >>/var/log/backup.log 2>&1
Enter fullscreen mode Exit fullscreen mode

flock prevents overlapping runs. Redirect stdout/stderr to a log you actually look at.

5) Permissions, SELinux, and executable bits

ls -l /usr/local/bin/backup.sh
sudo -u thecronuser /usr/local/bin/backup.sh
Enter fullscreen mode Exit fullscreen mode

Run as the same user cron uses. If that fails, cron will fail too. SELinux denials show up in audit logs — don’t disable SELinux to “make cron work”; fix the context.

Scripts under /etc/cron.daily need to be executable and often must not have extensions depending on run-parts behavior.

6) Percent signs, newlines, and special characters

In crontab, % becomes a newline unless escaped. Quoting matters. If your command looks like line noise, refresh Linux special characters and put complexity inside a script file instead of an inline novel.

7) Mail, pam, and silent failure

Cron traditionally mails output to the user. If mail isn’t configured, you see nothing. Prefer explicit logs.

Also check:

grep CRON /var/log/syslog   # Debian-ish
grep CROND /var/log/cron    # RHEL-ish
journalctl -u cron -b
Enter fullscreen mode Exit fullscreen mode

Did cron even attempt to start the job? If there’s no line at the scheduled time, you’re looking at schedule/daemon/user issues. If there is a line and the job exits non-zero, debug the script.

8) Disk full and readonly filesystems

Cron can’t write logs or temp files if the disk is full. If jobs vanish mysteriously after a growth spurt, run the disk full checklist.

9) Maybe you want a systemd timer

For new work on systemd distros, timers give better logging, dependencies, and systemctl list-timers.

systemctl list-timers --all
Enter fullscreen mode Exit fullscreen mode

Cron still fine for simple schedules. Timers shine when you care about catch-up (Persistent=true) and unit dependency graphs.

Pro checklist (copy into the ticket)

  1. crond/cron service active
  2. correct crontab / cron.d entry
  3. time zone understood
  4. absolute paths + logging redirect
  5. manual run as cron user succeeds
  6. logs show execution attempt
  7. exit code / script error fixed
  8. prevent overlap with flock
  9. monitoring: alert if the job’s output artifact is stale

Security note: cron as root with world-writable scripts is a gift to attackers. Keep scripts locked down the same way you treat SSH access.

Worked example: backup script “runs” but empty files

Cron line fires; logs show the script started; backup size is 0 bytes. Manual run as root works. Cause: cron user lacked permission to read the data directory; the script ignored set -e and “succeeded.”

Fixes:

  • set -euo pipefail at top of bash scripts
  • Check exit codes of mysqldump/tar explicitly
  • Alert if backup artifact size < threshold or mtime too old
  • Run verification restores monthly (tiny sample)

Gotchas beyond the basics

If the checklist above found nothing, look at these.

Crontab environment gotchas beyond PATH

  • HOME may differ
  • SHELL defaults to /bin/sh (dash on Debian) — bashisms fail
  • Locale variables missing → weird sorting/date formats
  • Secrets available in interactive pam sessions but not cron
* * * * * /usr/bin/env > /tmp/cron-env.txt
Enter fullscreen mode Exit fullscreen mode

Compare that file to your interactive env. Remove the debug line afterward.

Distributed systems note

Never schedule the same critical job on five app nodes without locking or a leader. You’ll get five overlapping dumps and a sad database. Use a single scheduler host, systemd timer on one node, or an orchestrator.

Migrating a job to a timer

Create backup.service (oneshot) + backup.timer. Benefits: clearer logs, dependencies, systemctl list-timers, and consistent unit permissions. Keep cron for simple legacy jobs if you must — but don’t run both for the same task.

Observability for scheduled work

A job without a freshness check is a wish. Minimum: dead man’s switch (Healthchecks.io, Prometheus pushgateway + alert, or “file mtime older than 26h”). Cron failing silently is how backups die for three months unnoticed.

Daylight saving and odd schedules

Jobs scheduled in the DST gap may skip or double depending on the cron implementation and timezone. Prefer UTC on servers when your org can handle it psychologically — or document local-time behavior during transitions.

Wrappers that save careers

#!/usr/bin/env bash
set -euo pipefail
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
cd /opt/jobs/backup
/usr/bin/flock -n /run/backup.lock /opt/jobs/backup/run.sh | /usr/bin/logger -t backup-job
Enter fullscreen mode Exit fullscreen mode

Call that wrapper from cron with an absolute path. Logging to syslog/journal means you can journalctl -t backup-job.

Permissions on /etc/cron.d files

Some distros ignore cron.d files that are group/world writable or have wrong names. If your drop-in never runs, check mode 644, root ownership, and no dots in the filename when run-parts is involved for daily directories.

Final sanity printout

At the scheduled minute, watch live:

journalctl -f | grep -i cron
Enter fullscreen mode Exit fullscreen mode

If nothing appears, the schedule/daemon/user path is wrong. If something appears and the job fails, debug the script — not cron itself.

Practice

Lab exercise

Create a user crontab that runs every minute and appends UTC timestamps to ~/cron-lab.log. Watch it work. Break PATH by calling a bare command that only exists in your interactive profile. Fix with absolute paths. Add flock. Remove the every-minute job when finished so you don’t wake up to a gigabyte log.

FAQ

Why does it run manually but not from cron?

Environment (PATH, cwd, secrets), different user, or missing tty assumptions. Log env from the script with env when debugging.

Do cron jobs run when the server was off?

Classic cron: no (missed jobs are missed). anacron/systemd Persistent timers: can catch up. Know which you have.

How do I run every 5 minutes?

*/5 * * * * /path/to/job
Enter fullscreen mode Exit fullscreen mode

Is @reboot reliable?

Mostly, after local filesystems are up — but network may not be. For networky jobs, prefer systemd with After=network-online.target.

Should secrets live in the crontab?

No. Use a root-only env file or a secrets manager. Crontab is often readable by the user and ends up in backups.

Sources


This article first appeared on sysadmin.co.in. The original has the latest updates.

Top comments (0)