Cron job not running? A troubleshooting checklist

Updated November 3, 2026

The command works when you run it by hand, the crontab line looks right, and yet nothing happens at 3 AM. Cron rarely says why: it has no dashboard, and by default it mails errors to a local mailbox nobody reads. Go through this checklist from top to bottom. The first items are the most common causes.

1. Is cron running at all?

systemctl status cron     # Debian, Ubuntu
systemctl status crond    # RHEL, Fedora, Rocky, Alma

In a Docker container there is usually no cron daemon unless you start one; see cron jobs in Docker.

2. Did cron try to start the job?

Cron logs every start it makes. If your line isn’t there, cron didn’t try to run it; if it is, the job started and failed on its own.

journalctl -u cron --since "today"     # or -u crond
grep CRON /var/log/syslog              # Debian, Ubuntu with rsyslog
sudo tail /var/log/cron                # RHEL family

3. The environment is almost empty

This is the classic one. Cron doesn’t read .bashrc or .profile. A job gets SHELL=/bin/sh, a short PATH such as /usr/bin:/bin, and none of your exported variables. So node, php, docker or aws from /usr/local/bin or a version manager simply isn’t found.

To reproduce cron’s environment in your shell, run the job with an empty environment:

env -i SHELL=/bin/sh PATH=/usr/bin:/bin HOME="$HOME" /bin/sh -c '/srv/app/job.sh'

4. A percent sign cuts the command

In a crontab, % means a new line, and everything after the first one is passed to the command as input. date +%F breaks the line silently. Escape it as \%, or move the command into a script:

0 3 * * * /srv/backup.sh > /var/backups/log-$(date +\%F).txt 2>&1

5. The script can’t be executed

6. The crontab file itself

7. The schedule isn’t what you think

Paste the expression into the cron expression explainer to see the next runs in plain words.

8. Errors go nowhere

Cron mails a job’s output to the user or to MAILTO. Without a mail server it’s lost. While debugging, write it to a file:

*/5 * * * * /srv/job.sh >> /var/log/job.log 2>&1

If nothing helps, add a temporary line * * * * * env > /tmp/cron-env, wait a minute and compare the file with env in your shell.

Know next time without checking

Every item above fails quietly. Instead of finding out weeks later, make the job report each run and get an alert when a report doesn’t come. Create a check in Heartcron with the same schedule and add a ping to the end of the line:

0 3 * * * /srv/backup.sh && curl -fsS -m 10 --retry 5 -o /dev/null https://ping.heartcron.com/<uuid>

If cron stops, the server is rebuilt without the crontab, or the script fails, the ping doesn’t arrive and you get an alert in Telegram, Slack, Discord or email. To also see the error text, send the output and the exit code:

0 3 * * * /srv/backup.sh 2>&1 | tail -c 100000 | curl -fsS -m 10 --data-binary @- https://ping.heartcron.com/<uuid>/${PIPESTATUS[0]}

This one needs SHELL=/bin/bash at the top of the crontab, because PIPESTATUS is a Bash feature. Or install the open-source heartcron CLI and let it do the same: heartcron run nightly-backup -- /srv/backup.sh.

More guides