Cron is the 50-year-old scheduler that still runs the internet's background jobs — Linux servers, GitHub Actions pipelines, database backups, certificate renewals. And its five-field syntax is famously terse: "0 9 * * 1-5" is perfectly clear once you know the pattern, and pure hieroglyphics before that. This guide walks through every field, every operator, and the two semantic traps that silently break schedules — with a free interactive explainer to test each example as you go.
The Five Fields
A cron expression has exactly five fields, separated by spaces, in this order:
- Minute (0–59) — the minute of the hour to run
- Hour (0–23) — the hour of the day, in 24-hour time
- Day of month (1–31) — which day(s) of the month
- Month (1–12, or JAN–DEC) — which month(s)
- Day of week (0–7, or SUN–SAT) — 0 and 7 are both Sunday
So "0 9 * * 1-5" reads: minute 0, hour 9, every day of the month, every month, days-of-week 1 through 5 (Monday–Friday) — i.e. every weekday at 9:00 AM. You can verify any expression interactively with the Cron Expression Explainer, which translates cron to plain English and shows the next five run times.
The Four Operators
- * (all values) — "every minute / every hour / every day", depending on the field
- , (list) — "15,30,45" means minute 15, 30, and 45
- - (range) — "9-17" means hours 9 AM through 5 PM inclusive
- / (step) — "*/15" means every 15th value: 0, 15, 30, 45
Operators combine: "10-40/10" means "starting at 10, through 40, every 10" — minutes 10, 20, 30, 40. Months and weekdays also accept names: "JAN,MAR" or "mon-fri" (case-insensitive in most implementations).
The @ Shortcuts
Common schedules have shorthand forms that replace the whole expression:
- @yearly / @annually — 0 0 1 1 * (once a year, midnight January 1)
- @monthly — 0 0 1 * * (midnight on the 1st)
- @weekly — 0 0 * * 0 (midnight every Sunday)
- @daily / @midnight — 0 0 * * * (every day at midnight)
- @hourly — 0 * * * * (top of every hour)
Note that @reboot also exists, but it is not a time-based schedule — it fires once when the cron daemon starts, and most schedulers (including GitHub Actions) do not support it.
Trap 1: The Day-of-Month / Day-of-Week OR Rule
This is the rule that surprises everyone. When BOTH day-of-month and day-of-week are restricted (neither is *), cron matches if EITHER field matches — not both. So "0 0 1 * 1" does NOT mean "the 1st of the month, only if it's a Monday". It means "every 1st of the month AND every Monday" — two sets of runs.
If you want "the 1st, but only when it's a Monday", standard cron cannot express it in one line; you schedule the 1st and let the job itself exit early on the wrong weekday (or use a modern scheduler like systemd timers). To get "every Monday" alone, just leave day-of-month as *: "0 0 * * 1".
Trap 2: Impossible Dates Silently Never Fire
"0 0 30 2 *" means February 30th — a date that does not exist. Cron accepts the expression without complaint, and the job simply never runs. No error, no log entry, nothing. The same applies to day 31 in 30-day months if you pinned a short month. Always preview the next several run times of a new expression — if the list is empty, your schedule is impossible.
Cron in GitHub Actions and Cloud Schedulers
GitHub Actions schedule triggers use standard cron, with two caveats: times are always UTC (your 9 AM deploy needs the UTC offset), and scheduled workflows can be delayed 15+ minutes during peak load — do not use them for minute-critical jobs. AWS EventBridge and Quartz use 6-field cron with seconds at the front, which is a different dialect — make sure you know which one your scheduler expects before pasting an expression.
Cheat Sheet
- * * * * * — every minute
- */15 * * * * — every 15 minutes
- 0 * * * * — every hour, on the hour
- 0 9 * * 1-5 — weekdays at 9:00 AM
- 0 0 * * 0 — every Sunday at midnight
- 0 0 1 * * — first of every month, midnight
- 0 0 1 1 * — once a year, January 1
- 30 4 * * 0 — 4:30 AM every Sunday (classic backup window)
The fastest way to internalize all of this is to experiment: paste expressions into the Cron Expression Explainer and watch the plain-English translation and next run times update live — it implements the exact Vixie-cron semantics described above, including the OR rule and impossible-date detection.