Cron expressions explained: the five fields, the special characters, and the traps
0 9 * * 1–5 runs something at nine every weekday, and the notation has looked like that since Unix in the 1970s. Cron expressions are terse, everywhere — servers, CI pipelines, cloud schedulers, Kubernetes — and easy to get subtly wrong. The cron parser translates any expression into English and lists the next runs; this guide teaches the reading.
The five fields
┌─ minute (0–59) │ ┌─ hour (0–23) │ │ ┌─ day of month (1–31) │ │ │ ┌─ month (1–12 or JAN–DEC) │ │ │ │ ┌─ day of week (0–6, Sunday = 0, or SUN–SAT) │ │ │ │ │ * * * * * command
Each field is a set of allowed values; the job runs when the current time matches all five (with one famous exception below). A * means "every value". 30 14 * * * is 14:30 daily; 0 0 1 * * is midnight on the first of every month.
The special characters
- * — any value.
- , — a list: 0 9,17 * * * runs at 9:00 and 17:00.
- - — a range: 0 9 * * 1-5 is weekdays.
- / — a step: */15 * * * * is every 15 minutes; 0 */2 * * * every two hours on the hour.
- Names — MON, JAN; case-insensitive in most implementations.
Some implementations add L (last), W (nearest weekday), # (nth weekday: 5#3 is the third Friday) and ? — Quartz (Java) and its descendants have them; classic Unix cron does not.
Common examples
*/5 * * * *— every five minutes.0 * * * *— every hour, on the hour.0 2 * * *— daily at 02:00 (backups love this slot).0 9 * * 1-5— weekdays at 09:00.0 0 * * 0— Sundays at midnight.0 0 1 * *— first of the month.0 6 1 1 *— 06:00 on 1 January.30 8 * * 1,3,5— 08:30 Monday, Wednesday, Friday.
The traps
- Day-of-month OR day-of-week. When both are restricted, classic cron runs if either matches: 0 9 13 * 5 fires on the 13th of every month and every Friday, not on Friday the 13th. Implementations differ; test.
- Sunday is 0 and 7 in most crons; some accept only 0.
- Time zone. Cron runs in the system's zone (often UTC on servers and in the cloud). A "9 a.m." job in UTC is 4 a.m. in New York and 10 a.m. in London — and shifts with daylight saving. Jobs scheduled at 02:30 in a zone with DST may run twice or never on changeover nights (Working across time zones).
- Every minute is 1,440 runs a day. A */1 job that takes two minutes overlaps itself; use a lock or a queue.
- 31st of the month doesn't exist in most months; 0 0 31 * * runs seven times a year. "Last day" needs L or a script.
- Missed runs during downtime are simply skipped; cron has no catch-up.
Variants: seconds, years, GitHub, Kubernetes
Quartz and Spring add a leading seconds field (six or seven fields with an optional year), so their expressions don't paste into Unix cron unchanged. GitHub Actions schedules use standard five-field syntax in UTC and may run late under load. Kubernetes CronJobs use five fields with a configurable time zone since 1.27. Systemd timers replace cron on many Linux systems with a different calendar syntax (Mon..Fri 09:00). Whichever you use, paste the expression into the parser and read the next five run times before trusting it; the timestamp converter checks the zone arithmetic, and the time zone converter the human side.
Sources and further reading
The claims in this guide rest on these references, which were checked when the guide was last updated. Spotted an error? The contact page says how to report it.