* * * * * │ │ │ │ │ │ │ │ │ └── day of week (0-6, Sunday=0) │ │ │ └──── month (1-12) │ │ └────── day of month (1-31) │ └──────── hour (0-23) └────────── minute (0-59)
The order is fixed and nothing about it is mnemonic — you just have to know minute comes before hour. Get this backwards once (write hour-then-minute out of habit from some other config format) and you get a job that fires at the wrong time every day, which is a genuinely annoying bug to spot because the schedule “looks right” at a glance.
Schedules people actually copy-paste
0 * * * *— every hour, on the hour*/15 * * * *— every 15 minutes0 0 * * *— once a day, at midnight0 9 * * 1-5— 9am, Monday through Friday0 0 1 * *— midnight on the 1st of every month0 0 * * 0— midnight every Sunday
Day-of-month AND day-of-week is an OR, not an AND
This is the sharpest edge in cron syntax. If both the day-of-month and day-of-week fields are restricted (not *), most cron implementations run the job if either matches, not only when both match. 0 0 15 * 5 doesn't mean “the 15th, if it's a Friday” — it means “the 15th of every month, AND every Friday.” If you actually want “the 15th, only if it's a Friday,” you need application-level logic, not cron syntax, because cron itself can't express that intersection.
Step values and ranges combine
10-30/5 in a minute field means every 5 minutes, but only between minutes 10 and 30 — so 10, 15, 20, 25, 30. Useful for “every 5 minutes during business hours” style schedules without needing a second cron line.