A little help goes a long way. Find a tool/

Developer FIELD NOTES · 4 MIN READ

How Cron Expressions Work

The five cron fields, the four special characters, worked examples, the day-of-month vs day-of-week OR quirk, and DST caveats.

A cron expression is five fields of schedule math that has run the world's batch jobs since 1970s Unix: one line that says "fire this command at these times, forever." The syntax looks cryptic, but it encodes exactly five questions — which minute, which hour, which day of the month, which month, which day of the week — and every symbol answers one of them.

Five fields, left to right

* * * * *
│ │ │ │ └── day of week   0–7  (0 and 7 both mean Sunday)
│ │ │ └──── month         1–12
│ │ └────── day of month  1–31
│ └──────── hour          0–23
└────────── minute        0–59

The scheduler wakes up every minute, checks whether the current time matches all five fields, and runs the job on a full match. So 30 14 * * * reads "minute 30, hour 14, any day, any month, any weekday" — 2:30 PM every day. Time values are numeric: 0 is midnight, 0 in the weekday field is Sunday, and months and weekdays are written as numbers (some crons also accept names like MON, but portable expressions stick to digits).

The four special characters

Four symbols do all the work in a cron field:

*          every value in the field's range
,          list:        0,15,30,45
-          range:       9-17
/          step:        */5

* means "every allowed value". A comma-separated list matches any of its members. A hyphen is an inclusive range. The step /n skips through a range in increments of n — and here is the detail most people miss: the step starts from the field's minimum, not from when you wrote the file. */15 in the minute field means 0, 15, 30, 45 — fire at :00, :15, :30, :45 past every hour. It is wall-clock aligned, not "15 minutes after the previous run". Steps can also ride on ranges: 10-50/10 in minutes means 10, 20, 30, 40, 50.

Reading real expressions

0 9 * * 1-5        09:00, Monday through Friday
0 12 * * 1-5       noon every weekday
0 0 1 * *          midnight on the 1st of every month
15 14 * * 0        14:15 every Sunday
*/15 9-17 * * *    every 15 minutes, 09:00–17:45, every day
0 4 * * 1          04:00 every Monday

Read them field by field and they become plain sentences. The classic maintenance pattern 0 4 * * 1 — "at 04:00, any day-of-month, any month, on Mondays" — is why so many systems update at 4 AM Monday: quiet traffic hours and a full working week ahead to notice breakage.

The day-of-month / day-of-week quirk

Here is the bug hiding in many tutorials. In classic Vixie cron, when both day-of-month and day-of-week are restricted (neither is *), the job runs when either matches — an OR, not an AND. So 0 0 13 * 1 fires on the 13th or on every Monday, not on "Mondays that fall on the 13th". If you meant AND, spell one side out differently or split the job into two expressions.

Implementation behavior varies: some modern schedulers apply AND across all five fields. The safe habit is to preview the next few run times before trusting any expression that uses both day fields — which is exactly what a next-run calculator is for.

What cron does not do

Standard cron has no seconds — five fields, finest resolution one minute. Runs are wall-clock aligned, so a thousand jobs set to 0 * * * * all pile onto the same second (big platforms rate-limit the top of the hour; add jitter by moving some jobs to 1,2). Cron schedules local time, which collides with daylight saving: 2:30 AM on a spring-forward day never happens, so that run is skipped, and on a fall-back day it happens twice. This is why servers commonly run cron in UTC — one less moving part, as How Time Zones Work explains, and the Time Zone Converter translates a wall-clock time into whatever zone your scheduler really runs in. And if the machine is down at the scheduled moment, the run is missed, not queued: classic cron does not catch up.

Try it yourself

Build an expression from human choices — pick the minutes, hours, and days and get the five fields — with the Cron Expression Generator, which also writes a plain-English description. Then paste any expression into the Cron Next Run Calculator to see the actual upcoming fire times. Its parser takes five numeric fields (0–59, 0–23, 1–31, 1–12, 0–7 with 7 equal to Sunday), supports *, lists, ranges, and steps, and requires all five fields to match — and because it projects times in UTC, it doubles as a reality check for how a UTC-scheduled server will treat your expression.

Frequently asked questions

What do the five cron fields mean?
Left to right: minute (0–59), hour (0–23), day of month (1–31), month (1–12), and day of week (0–7, where both 0 and 7 mean Sunday). The scheduler fires a job when the current time matches all five fields, checking once per minute.
What does */15 mean in a cron expression?
A step: start at the field's minimum and repeat every 15 values. In the minute field, */15 means 0, 15, 30, 45 — firing at quarter-hours of the clock, not 15 minutes after the previous run. Steps can also ride on ranges, like 10-50/10.
What is the day-of-month vs day-of-week quirk?
In classic Vixie cron, when both day-of-month and day-of-week are restricted (neither is *), the job runs when EITHER matches — 0 0 13 * 1 runs on the 13th or on Mondays, not on Mondays the 13th. Some modern schedulers treat the fields as AND, so preview the next run times to be sure.
Do cron jobs run in UTC?
Cron schedules local server time by default, which collides with daylight saving: a 2:30 AM run is skipped on spring-forward days and fires twice on fall-back days. Many servers set cron to UTC to remove the ambiguity — check which zone your scheduler uses before trusting the wall-clock time.
Why did my cron job not run while the server was down?
Classic cron checks every minute and has no memory: a moment missed is a run missed, not queued. If catch-up matters, use anacron-style schedulers, systemd timers with persistent timestamps, or job queues — or make the job itself idempotent and cheap to re-run.