Cron syntax: the five fields explained
Cron has exactly five fields. Each field specifies a time dimension. A job runs when all five fields match the current time simultaneously.
| Position | Field | Range | Notes |
|---|---|---|---|
| 1 | Minute | 0–59 | Minutes past the hour |
| 2 | Hour | 0–23 | 24-hour clock; 0 = midnight |
| 3 | Day of month | 1–31 | Calendar date; not all months have 31 days |
| 4 | Month | 1–12 | Also accepts Jan–Dec abbreviations |
| 5 | Day of week | 0–7 | 0 and 7 both mean Sunday; also accepts Sun–Sat |
The expression 0 9 * * 1-5 reads as: minute 0, hour 9, any day of month, any month, Monday through Friday. Result: 9:00 AM every weekday. The wildcard * means "every valid value for this field."
Special characters: *, /, -, and ,
Asterisk * — matches every value in the field's range. * * * * * runs every minute of every day.
Slash / — defines a step interval. */10 in the minute field means "every 10 minutes." 0-30/5 means "every 5 minutes from :00 to :30." The step divides the range, not the absolute clock — */7 in hours fires at 0, 7, 14, 21 (not "every 7 hours from now").
Hyphen - — defines an inclusive range. 1-5 in the day-of-week field means Monday through Friday. 9-17 in the hour field means 9am through 5pm.
Comma , — defines a list of values. 0,15,30,45 in the minute field fires four times per hour, on the quarter-hours. 1,3,5 in month fires in January, March, and May. You can mix commas with ranges: 0,6,12-14.
20 common cron expressions and what they do
These cover 90% of production scheduling needs. Paste any into the parser above to verify the next run times before deploying.
Four cron gotchas that bite production systems
1. Day-of-month OR day-of-week, not AND. Most Unix cron implementations use OR semantics when both day-of-month and day-of-week are non-wildcard. 0 0 15 * 5 fires on the 15th of every month AND every Friday at midnight — not just "the 15th if it falls on a Friday." Use * in whichever field you don't want to constrain.
2. Timezone is the server's timezone, not yours. A job scheduled for 0 9 * * * runs at 9am in whatever timezone the cron daemon is configured for — usually UTC on cloud VMs. A Vancouver-based developer thinking "9am" will get a 2am local job (UTC-7). Always explicitly set CRON_TZ or use platform-level timezone settings.
3. Daylight Saving Time creates ghost runs and missed runs. When clocks spring forward, 2:30am never exists — a job scheduled for that time is silently skipped. When clocks fall back, 1:30am runs twice. If you have jobs near DST transition times, schedule them at safe hours (midnight, 3am) and monitor for double-runs on the fall-back Sunday.
4. */n is not "every n units from now." */7 in the hour field does NOT mean "every 7 hours from when the daemon started." It means "every hour whose value is divisible by 7" — so 0, 7, 14, 21. That is 4 runs per day, not 3.4. If you want a job every 7 hours, you need a different mechanism (a wrapper script with a sleep, or a platform that supports duration-based scheduling like GitHub Actions' schedule with a manual offset).
Cron in Cloudflare Workers and serverless platforms
Traditional cron lives on a server. Modern serverless platforms expose the same syntax but with important differences.
Cloudflare Workers Cron Triggers — defined in wrangler.toml under [triggers]. They use standard cron syntax and always fire in UTC. The maximum interval between runs is once per minute; the minimum is also enforced (you can't go faster than once per minute). Cron Triggers invoke your Worker's scheduled() handler, not fetch(). Cloudflare Workers is genuinely the easiest way to run a globally distributed cron job — no server, no uptime management, 100K free invocations per day on the free tier.
GitHub Actions scheduled workflows — use the schedule event with a cron expression under on. Note: GitHub Actions schedules can be delayed by up to 1 hour during high-load periods. They're suitable for non-time-critical jobs (weekly reports, dependency updates) but not for anything requiring second-level precision.
AWS EventBridge Scheduler — supports both cron expressions and rate expressions (rate(15 minutes)). Unlike vanilla cron, EventBridge supports a 6-field variant with a seconds field. It also supports timezone-aware scheduling via the --schedule-expression-timezone parameter, which is the correct solution for the UTC confusion problem.
Whichever platform you use, test the expression here first, confirm the next 10 runs match your intent, and check the platform's UTC-vs-local documentation before deploying.
Ship your cron job to the edge
Cloudflare Workers Cron Triggers run globally in UTC, require no server, and cost nothing on the free tier (100K invocations/day). If you're still SSHing into a VPS to run crontab -e, there's a better option.