Cron Expressions Explained: How to Read Any Schedule

⚙️ Developer⏱️ 8 min readFree tool included

Every cron expression is five tiny filters wearing a trench coat. Once you see the fields — minute, hour, day of month, month, day of week — strings like 30 4 1,15 * * stop being intimidating and start being almost boring: 4:30 AM on the 1st and 15th. This guide builds the reading skill from scratch, covers the Quartz extras you'll meet in Java schedulers and cloud consoles, and shows how to verify a schedule by looking at when it actually fires next.

Advertisement

What are the five fields, in order?

Read left to right, smallest unit to biggest:

PositionFieldValuesNames allowed
1Minute0–59no
2Hour0–23no
3Day of month1–31no
4Month1–12JAN–DEC
5Day of week0–6 (0 = Sunday)SUN–SAT

The rule that makes everything click: a job runs at every minute where all five fields match at the same time. An asterisk is a wildcard that matches everything, so * * * * * is "every minute of every day." Change only the first field to */15 and you get every fifteen minutes. The four *s didn't change — you just tightened one filter.

How do steps, ranges, and lists work?

Each field accepts four building blocks, and you can combine them:

A classic business-hours check, */15 9-17 * * 1-5, reads: every 15 minutes, during the hours 9:00–17:59, any day of the month, any month, Monday through Friday. Five fields, five filters, one sentence.

Why is Sunday both 0 and 7?

Historical accident worth 30 seconds: original Unix cron numbered Sunday 0. ISO 8601 later standardized Monday 1 through Sunday 7, and to keep both camps happy, most implementations accept 0 and 7 as Sunday. Practical consequence: 1-5 is weekdays in both systems, but 6-7 vs 0-6 style weekend ranges should be written 6,0 or SAT,SUN to be unambiguous everywhere. When in doubt, use the names — every modern parser understands MON-FRI.

What do L, W, #, and ? mean?

These are Quartz additions (also supported by many cloud schedulers) for the things five plain fields can't say:

SymbolFieldMeansExample
Lday of monthlast day of the month0 12 L * * — noon on the 28th/29th/30th/31st
nLday of weeklast given weekday of the month6L — last Friday
Wday of monthweekday nearest that date15W — the 15th, or the closest weekday
n#mday of weekthe m-th weekday n of the month2#1 — first Tuesday (patch Tuesday)
?day fieldsno specific valuerequired by Quartz when the other day field is set

The ? rule trips people up in Quartz specifically: you may not restrict day-of-month and day-of-week at once, so whenever one of them names real days, the other becomes ?. Standard five-field cron has no such rule — there, when both day fields are restricted, many implementations fire on either match (OR), not both. That OR is the single most surprising cron behavior, and it's why 0 0 13 * 5 means "the 13th, plus every Friday" on a classic crontab, not "Friday the 13th."

What are @daily, @hourly, and the other shortcuts?

Nicknames for schedules everyone writes constantly:

NicknameExpands toRuns at
@hourly0 * * * *top of every hour
@daily / @midnight0 0 * * *midnight every day
@weekly0 0 * * 0midnight Sunday
@monthly0 0 1 * *midnight on the 1st
@yearly / @annually0 0 1 1 *midnight January 1st

Why did my job run an hour late (or twice)?

Two usual suspects. Timezones: crontab evaluates in the server's local time by default, so a job set for 9:00 on a UTC box fires at 9:00 UTC — 5 AM in New York. Cloud schedulers let you pin an IANA zone, which is always worth doing for anything user-facing. DST: when clocks spring forward, the 2:00–3:00 AM hour doesn't exist and a job scheduled inside it either runs late or is skipped entirely; when clocks fall back, a job in the repeated hour can fire twice. Schedules at :00 past odd hours (like 3:15) sidestep both edges.

The reliable habit: before trusting any expression, look at its next few actual fire times — not the fields, the dates. If 0 9 * * MON-FRI shows you Tue 09:00, Wed 09:00, Thu 09:00, Fri 09:00, then Mon 09:00, you can see the weekend skip with your own eyes instead of reasoning about it.

Decode any cron expression, free

Paste an expression, get the English sentence plus its next five run times in your timezone.

Open the Cron Explainer

Reading practice: five real expressions

If you got all five without peeking, you can read cron. When you need to write one instead, a field-by-field cron generator is the safer route — compose it, then paste the result into the explainer to confirm it says what you meant. And when the schedule itself isn't the mystery but "when did it last run" is, convert fire times with an epoch converter to see them alongside your logs.

Advertisement