Vivere

Vivere / Answers

Cron job not running: how to find out why

Cron fails quietly by design. Here is the order to check things in, from the five causes that account for almost every case.

Cron does not tell you when a job fails, and it does not tell you when a job never started. It was written for an era when the machine had a local mail spool and somebody read it. The result is that a broken cron job looks exactly like a working one until somebody notices the consequences.

Work through these in order. The first three cover almost every case.

1. Did cron try to run it?

This is the fork in the road: if cron started the job, you have a job problem; if it did not, you have a cron problem. Cron logs every launch it attempts.

# Debian, Ubuntu
journalctl -u cron --since "2 days ago" | grep your-script
grep CRON /var/log/syslog

# RHEL, Fedora, Amazon Linux
journalctl -u crond --since "2 days ago"
grep CROND /var/log/cron

A line naming your command means cron ran it and the problem is inside the job. No line at all means cron never started it, and the next two sections apply.

Note what this log does not contain: the job's own output, or its exit code. Cron records the launch, not the outcome.

2. Are you looking at the crontab that is actually installed?

There are at least four places a schedule can live, and editing the wrong one is a common way to lose an afternoon:

Two file-level traps: a crontab whose last line has no trailing newline may have that line ignored, and a % anywhere in the command is turned into a newline unless you escape it as \%. That one bites every date +%Y-%m-%d in a crontab.

3. Does the job work in cron's environment?

Cron runs your command with almost nothing set. No .bashrc, no .profile, a PATH of roughly /usr/bin:/bin, SHELL set to /bin/sh rather than bash, and the user's home as the working directory. A script that works in your terminal and fails under cron is nearly always this.

Reproduce it rather than guessing:

env -i /bin/sh -c 'cd /; /path/to/your/script.sh'

The fix is to be explicit. Use absolute paths for the interpreter and the script, activate the virtualenv by calling its interpreter directly, and cd to the working directory the script expects:

0 2 * * * cd /srv/app && /srv/app/.venv/bin/python /srv/app/export.py

4. It ran, and it failed

Cron mails output to the crontab's owner, which on most cloud images goes nowhere at all. Capture it yourself:

0 2 * * * /srv/app/export.sh >> /var/log/export.log 2>&1

Do not use > /dev/null 2>&1 on a job you care about. It is the single most effective way to make a failure invisible.

5. The machine, the clock and the timezone

Cron fires in the system timezone, which on servers is usually UTC. Check with timedatectl. A daily job scheduled for 02:00 local time on a UTC machine runs at a different hour than you intended, and around a daylight-saving change a job scheduled in a DST-observing zone can run twice or not at all.

And if the machine was suspended or powered off when the job was due, plain cron simply skips it. anacron exists precisely to catch up jobs on machines that are not always on; a laptop or a VM that is stopped overnight needs it, or a timer with Persistent=true.

How to find out next time without looking

Everything above is diagnosis after the fact. The reason it took so long to notice is that no log can record the run that never happened. A job that was never started writes nothing, and a machine that was off writes nothing at all.

The reliable answer is to make the job report to something outside the machine on every success, and to be alerted when that report does not arrive on time. That is a dead man's switch, and it is one line:

0 2 * * * /srv/app/export.sh && curl -fsS -m 10 --retry 3 -o /dev/null https://vivere.dev/p/<your-monitor-id>

Because of the &&, the ping only happens if the script exits zero. If the script fails, if cron never runs it, if the machine is off, or if the network is down, the ping does not arrive and you are told. Report the exit code instead of a bare success and you get the failure immediately rather than at the deadline:

0 2 * * * /srv/app/export.sh; curl -fsS -m 10 -o /dev/null https://vivere.dev/p/<id>/$?

Common questions

Why does my cron job work manually but not from crontab?

Cron runs with a nearly empty environment: no PATH beyond /usr/bin:/bin, no shell profile, and a different working directory. A command that relies on a PATH entry, a virtualenv, or a relative path works in your shell and fails under cron. Use absolute paths for both the interpreter and the script.

How do I see cron logs on Linux?

On Debian and Ubuntu, journalctl -u cron or /var/log/syslog filtered on CRON. On RHEL, Fedora and Amazon Linux, journalctl -u crond or /var/log/cron. These record that cron started the job, not what the job printed, so redirect the job's own output to a file if you want it.

How do I know a cron job did not run at all?

Nothing on the machine will tell you: cron logs a job it started and is silent about one it never started. The only reliable way is to have the job report to something outside the machine each time it succeeds, and to be alerted when that report does not arrive.

Find out without looking

Vivere watches for the ping that does not arrive. Add one line to the job, pick where alerts should land, and you hear about the run that never happened.

Start free Read the quickstart

Ten monitors, a status page, email and webhook alerts. No card.

Last reviewed September 2026.

Related