A cron expression is five fields of numbers and symbols that tell a scheduler exactly when to run a job. Most developers copy one from a tutorial, paste it into a crontab, and move on without reading it — until the job fires at the wrong time, or fires twice a day instead of once, and there is no obvious reason why. The five fields are not hard once you know what each one means and, more importantly, how the last two interact.
The order is fixed: minute, hour, day-of-month, month, day-of-week. 0 9 * * 1-5 reads as minute 0, hour 9, every day-of-month, every month, Monday through Friday — 9:00 AM on weekdays.
The Five Fields and Their Ranges
Each field accepts a specific range of values, and a value outside that range is invalid, not clamped:
- Minute: 0–59
- Hour: 0–23, in 24-hour time
- Day of month: 1–31
- Month: 1–12
- Day of week: 0–6, where 0 is Sunday and 6 is Saturday
The Four Symbols That Do the Work
An asterisk (*) means "every value" for that field. A range like 1-5 means every value from 1 through 5 inclusive. A step, written */15 or 0-30/10, means "every Nth value" starting from the beginning of the range — */15 in the minute field fires at :00, :15, :30, and :45. A comma list, like 1,15, means exactly those values and nothing between them.
Combining symbols in one field is normal: 0,30 9-17 * * 1-5 fires at the top and bottom of every hour, from 9 AM through 5 PM, Monday through Friday.
The Rule That Trips Up Almost Everyone
Day-of-month and day-of-week are the two fields most developers get wrong, because the interaction between them is not intuitive. When both fields are wildcards (*), the job runs on every day, obviously. When only one of the two is restricted and the other is a wildcard, the job runs on days matching the restricted field — also straightforward.
The confusing case is when both fields are restricted at once. Standard cron does not require both conditions to be true. It fires when either one matches — an OR, not an AND.
Take 0 0 15 * 5: midnight, on day-of-month 15, in any month, on day-of-week 5 (Friday). A developer reading this literally expects it to fire only when the 15th falls on a Friday. It does not work that way. It fires on the 15th of every month, regardless of what weekday that is, and it fires on every Friday, regardless of the date. Two separate trigger conditions, joined by OR, running independently.
Working Through a Real Calendar
Checking 0 0 15 * 5 against three actual dates makes the OR logic concrete. January 15, 2026 falls on a Thursday — not a Friday — but the job still fires, because day-of-month 15 matched on its own. January 16, 2026 is a Friday, not the 15th, and the job fires again, because day-of-week 5 matched independently. January 6, 2026, a Tuesday that is not the 15th, matches neither condition, so nothing fires. Three dates, two firings, for reasons that have nothing to do with each other.
If the intent really was "only when the 15th is a Friday," a cron expression alone cannot express that condition — it requires a wrapper script that checks the day of the week and exits early on any date the schedule fires but the condition is not met.
Common Schedules Worth Memorizing
*/15 * * * *— every 15 minutes, all day, every day0 0 * * *— once a day, at midnight0 9 * * 1-5— 9 AM on weekdays only0 0 1 * *— midnight on the first day of every month0 0 * * 0— midnight every Sunday
Frequently Asked Questions
Why does my job with both a day-of-month and day-of-week restriction run more often than I expected?
Because the two fields are joined by OR, not AND. Restricting both fields does not narrow the schedule to their intersection — it expands it to their union, which is the opposite of what most people assume.
How do I schedule a job for a specific day of the month only, regardless of weekday?
Leave day-of-week as a wildcard (*) and restrict only day-of-month, for example 0 0 15 * * for midnight on the 15th of every month with no weekday interaction.
What does a step value starting mid-range actually cover?
A step like 10-50/10 in the minute field starts at 10 and adds 10 repeatedly until it exceeds the upper bound — firing at :10, :20, :30, :40, and :50, not the full-hour pattern a plain */10 would produce.
Does day-of-week 7 also mean Sunday, like some cron implementations allow?
Some cron implementations accept both 0 and 7 for Sunday. This tool accepts the standard range of 0 through 6, with 0 as Sunday, and treats 7 as invalid input.
Can I combine a range and a step in the same field?
Yes — a-b/n is valid syntax. It applies the step within the bounds of the range rather than across the field's full range.
Paste your own expression into the Cron Expression Calculator to see it translated into plain English and its next ten actual run times, rather than working through the OR logic by hand each time.