cron · scheduling · unix
What is cron, exactly?
A cron entry is five time fields and a command. In order: minute, hour, day of month, month, day of week.
30 4 * * 1 /usr/local/bin/backup.sh That reads: minute 30, hour 4, any day of the month, any month, day of week 1. Half past four in the morning, on Mondays.
The vocabulary of a field
Each field takes more than a single number. * means every value. 1-5 is a range. 1,15 is a list. */15 is a step - every fifteenth value, so in the minute field that is four times an hour.
Two of the fields also accept names: months as JAN to DEC, days of the week as SUN to SAT. And the day-of-week field has a well-known oddity: both 0 and 7 mean Sunday in most implementations.
The field that is an OR
Here is the part that surprises people, and it is worth reading twice.
Day-of-month and day-of-week are two different ways of naming the same day. When you fill in only one of them and leave the other as *, cron behaves exactly as you expect. When you fill in both, POSIX specifies that a day matching either field triggers the job.
It is an OR, not an AND.
So 0 0 1 * 1 does not mean "the first of the month, if it is a Monday". It means the first of every month, and every Monday - which is roughly five times as many runs as intended.
The reasoning behind the choice is defensible. An AND would make most combinations fire almost never: "the 31st when it is a Friday" happens once or twice a year. The OR lets one line express "monthly and weekly" without a second entry. But it is the opposite of what the syntax looks like, and it is where scheduled jobs quietly run on the wrong days.
Shorthands, and why they are not portable
Many implementations accept @daily, @hourly, @weekly, @monthly and @reboot. They are convenient and they are not part of POSIX - they come from Vixie cron and its descendants.
On a machine you control, fine. In something that has to run anywhere - a container image, a script you distribute - the five explicit fields are the safer choice. @reboot in particular has no equivalent at all in the standard, and its behaviour depends entirely on the implementation.
What cron does not do
It does not catch up. If the machine was off at 4:30, the 4:30 job did not run and will not be rerun. Cron fires on wall-clock matches, it does not keep a queue of missed work.
It does not give you your login environment. A cron job runs with a minimal PATH and none of your shell profile. The single most common "it works in my terminal but not in cron" is a command found through a PATH that cron does not have. Use absolute paths.
It does not prevent overlap. If a job scheduled every five minutes takes six, you get two copies running. Cron will start the next one regardless; a lock file is your responsibility, not its.
It does not tell you it failed. Output goes to mail if mail is configured, and into nothing if it is not. Redirect to a log file and check it, or you will find out from the missing backup.
Read the expression before you trust it
Because the OR is invisible in the syntax, an expression that looks right can be wrong in a way that only shows up weeks later. Decoding it field by field before deploying costs a few seconds - that is what the cron expression decoder on this site is for.
A calendar showing weekday names down the side and day numbers across the grid. Cron has a field for each, and when you fill in both, it matches either one - not both together.