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.