Cron Expressions Explained: A Plain-English Guide to `* * * * *`
Five fields, a handful of special characters, and one genuinely surprising gotcha about how day-of-month and day-of-week combine. Everything you need to stop guessing at cron syntax.
Cron syntax is one of those things every developer eventually needs and nobody quite remembers between uses. Five fields, a few symbols, and one rule that trips up almost everyone the first time they hit it. Here's the whole thing.
The five fields
A standard cron expression is five space-separated fields, in this order:
┌───────────── minute (0-59)
│ ┌───────────── hour (0-23)
│ │ ┌───────────── day of month (1-31)
│ │ │ ┌───────────── month (1-12)
│ │ │ │ ┌───────────── day of week (0-6, Sunday = 0)
│ │ │ │ │
* * * * *
So 0 9 * * 1 reads as: minute 0, hour 9, any day of month, any month, day-of-week 1 (Monday) — every Monday at 9:00 AM.
(Some systems, including Kubernetes CronJobs and most standard Unix cron, use exactly this 5-field format. A few tools add a sixth "seconds" field at the front — check your specific system's docs if a schedule isn't firing when you expect.)
The special characters
*— "any value."* * * * *means every minute, of every hour, of every day.,— a list.0 9,17 * * *runs at 9:00 AM and 5:00 PM.-— a range.0 9-17 * * *runs every hour, on the hour, from 9 AM through 5 PM./— a step.*/15 * * * *runs every 15 minutes;0 */2 * * *runs every 2 hours, on the hour.
Combined: 0 9-17/2 * * 1-5 runs at 9 AM, 11 AM, 1 PM, 3 PM and 5 PM, Monday through Friday.
Common patterns
| Schedule | Expression |
|---|---|
| Every minute | * * * * * |
| Every 5 minutes | */5 * * * * |
| Every hour, on the hour | 0 * * * * |
| Every day at midnight | 0 0 * * * |
| Every day at 9:00 AM | 0 9 * * * |
| Every Monday at 9:00 AM | 0 9 * * 1 |
| First day of every month, midnight | 0 0 1 * * |
| Every weekday at 6:00 PM | 0 18 * * 1-5 |
The gotcha: day-of-month and day-of-week are OR'd, not AND'd
This is the single most common source of "why did my job run on a day I didn't expect" confusion. If both the day-of-month and day-of-week fields are restricted (not *), most cron implementations treat them as an OR, not an AND — the job runs if either condition is true.
So 0 0 15 * 1 does not mean "the 15th, but only if it's a Monday." It means "midnight on the 15th of every month, and midnight every Monday" — both conditions independently trigger it. If you actually want "only the 15th, and only if it's a Monday," you generally need to leave one of the two fields as * and add the other constraint in your job's own logic, since cron syntax alone can't express a true AND between those two fields.
Two more things worth knowing
Timezone. Cron schedules typically run in the system (or container's) local timezone unless the scheduler is explicitly configured otherwise — daylight saving transitions can shift an "every day at 2 AM" job in ways that surprise people twice a year. If your schedule is timezone-sensitive, check how your specific platform (cron daemon, Kubernetes CronJob, a cloud scheduler) resolves it, since this does vary.
@ shorthand. Many implementations (including standard Unix cron) support named shortcuts: @daily (0 0 * * *), @hourly (0 * * * *), @weekly, @monthly, @yearly. Handy for readability, but not universally supported — check your target system before relying on them.
Building or debugging a schedule right now? Build it visually and get a plain-English description instead of counting fields by hand.