Cron: The Five Fields, and What Bites in Production

The syntax takes ten minutes. The environment, the overlap and the missing hour take longer.

A cron expression is five fields: minute, hour, day of month, month, day of week.

*  *  *  *  *
│  │  │  │  └─ day of week (0–6, Sunday = 0)
│  │  │  └─── month (1–12)
│  │  └───── day of month (1–31)
│  └─────── hour (0–23)
└────────── minute (0–59)

Operators: * every value, , a list, - a range, / a step. So */15 9-17 * * 1-5 is every fifteen minutes, nine to five, Monday to Friday.

The day-of-month / day-of-week trap

If both day fields are restricted, cron runs when either matches — not both. 0 0 1 * 1 is not "the first of the month, if it is a Monday"; it is "the first of the month, and also every Monday". This is the most common misreading of the syntax, and the schedule that results fires far more often than intended.

The environment is not your shell

Cron runs with a minimal environment. PATH is typically /usr/bin:/bin — no ~/.bashrc, no ~/.profile, none of the version managers that put your real interpreter on the path.

The classic symptom is a job that works perfectly by hand and fails silently on schedule, because python3 resolved to a different interpreter without your packages, or node was not found at all. The fix is to use absolute paths for interpreters and binaries, and to set any variable the job needs inside the crontab or a wrapper script rather than assuming it.

Overlap

Cron starts a job on schedule whether or not the previous run has finished. A job scheduled every five minutes that occasionally takes seven will quietly accumulate concurrent copies, and the failure mode is a server that is fine for weeks and then is not.

Guard it with a lock: flock -n /var/lock/myjob.lock in front of the command exits immediately if the previous run holds the lock. Do not rely on the job "usually" being quick.

Output disappears

Anything a job writes to stdout or stderr is mailed to the crontab owner — and on most servers no mail transport is configured, so it goes nowhere. A job that has been failing for months looks identical to one that has been working.

Redirect explicitly: >> /path/to/job.log 2>&1. Appending both streams to a file you can actually read is the difference between an error you find and an error you do not.

Clocks and time zones

Cron uses the system time zone. On a machine observing daylight saving, a job scheduled at 02:30 does not run at all on the spring-forward day and runs twice on the autumn one. Anything that must not be skipped or repeated belongs outside 01:00–03:00, or on a machine set to UTC.

The special strings

@hourly, @daily, @weekly, @monthly, @yearly and @reboot are supported by most implementations. They read better than the numeric equivalents. Note that @daily is midnight exactly — which on a busy host means competing with every other @daily job, so a deliberate offset such as 17 3 * * * is usually kinder.

A checklist before you install one

Build and decode cron expressions → · Back to all articles