Cron Expressions Explained: Read and Write Any Schedule
Five fields, four symbols, one famous trap. That’s all of cron — and the trap is the part nobody warns you about.

A cron expression is five fields in a fixed order — minute (0–59), hour (0–23), day of month (1–31), month (1–12), day of week (0–7, with 0 and 7 both meaning Sunday) — read left to right, smallest unit first. Four symbols do the work: * for every value, commas for lists, hyphens for ranges, slashes for steps, so */15 9-17 * * 1-5 means every 15 minutes during office hours on weekdays. The one rule that surprises everyone: if both day of month and day of week are restricted, the job runs when either matches, not when both do.
Five fields, read left to right
A cron expression is a schedule written as five fields in a fixed order: minute, hour, day of month, month, day of week. The daemon checks the line every minute, and when the current time matches every field, it runs your command. That’s the whole machine. Everything else is notation.

| Field | Allowed values | Example | Means |
|---|---|---|---|
| Minute | 0–59 | 30 | at minute 30 of the hour |
| Hour | 0–23 | 9 | at 9 a.m. (24-hour clock) |
| Day of month | 1–31 | 1 | on the 1st of the month |
| Month | 1–12 (or JAN–DEC) | 6 | in June |
| Day of week | 0–7 (or SUN–SAT); both 0 and 7 are Sunday | 1 | on Mondays |
So 30 9 * * 1 reads as: minute 30, hour 9, any day of the month, any month, on Mondays — every Monday at 09:30. People trip on the order because Americans write dates month-first and cron writes time-first. Just read it like a clock that keeps zooming out.
The four symbols that do all the work
You only need four operators. A star (*) means “every allowed value” — every minute, every month, whatever the field allows. A comma builds a list: 1,15 in day of month means the 1st and the 15th. A hyphen builds a range: 9-17 in the hour field means 9 through 17. And a slash adds a step: */15 in the minute field means every 15 minutes, while 9-17/2 means every second hour from 9 to 17.
Combine them and most real schedules fall out. */15 9-17 * * 1-5 is the classic office-hours job: every 15 minutes, between 09:00 and 17:45, Monday to Friday. Once you can read that one without pausing, you can read nearly anything you’ll meet in the wild. Try it: what does 0 0 29 2 * do? It runs at midnight on 29 February — which is to say, in leap years only.
The trap: day of month meets day of week
Here’s the one that bites everyone. When both the day-of-month and day-of-week fields are restricted — neither is a star — standard cron doesn’t require both to match. It runs when either matches. So 0 9 1 * 1 doesn’t mean “09:00 on a Monday that’s also the 1st.” It means 09:00 on the 1st of every month, and at 09:00 every Monday. Roughly five runs a month where you expected one.
It’s not a bug; it’s how Vixie cron, the ancestor of most cron daemons in use today, defined it, and the man page documents it. But nobody reads the man page until the invoices go out twice. If you need “first Monday of the month,” cron alone can’t say it — schedule it for Mondays and let the command itself check that the date is on or before the 7th.
Write one from scratch
- Say the schedule in plain words first, out loud if you have to: “every weekday at half past nine in the morning.”
- Fill the fields in order: minute 30, hour 9, day of month
*, month*, day of week1-5. You get30 9 * * 1-5. - Check the two restricted fields: here only day of week is restricted, so the OR trap doesn’t apply. Good.
- Preview the next few run times before you save anything. Your eyes will catch what your brain glossed over — the tool below does this in one paste.
- Ask what happens on the schedule’s bad days: the 31st that never comes in February, the clock change in spring, the run that’s still going when the next one starts.
Shortcuts and dialects
Most cron daemons also accept nickname strings in place of the five fields: @hourly is 0 * * * *, @daily (and its alias @midnight) is 0 0 * * *, @weekly is 0 0 * * 0, @monthly is 0 0 1 * *, @yearly (alias @annually) is 0 0 1 1 *, and @reboot runs once at daemon startup rather than at a time at all.
Beyond that, dialects wander. Some systems add a sixth field for seconds at the front; Quartz-style schedulers put seconds first and may add a year field, and they use ? and other special characters that classic cron doesn’t understand. GitHub Actions, Kubernetes CronJobs and cloud schedulers each follow the classic five fields closely but not identically — Kubernetes, for instance, follows the same either-field behaviour for day of month and day of week. Don’t paste an expression from one dialect into another and assume. Check the target’s own docs, then preview the run times.
One last thing cron won’t tell you: which clock it’s reading. A server set to UTC runs your “9 a.m. report” at 9 a.m. UTC, and daylight-saving changes can skip a run or fire it twice in systems that use local time. The expression can be perfect and the timezone still wrong. Both halves are the schedule.
One more pattern habit transfers directly: a cron field is a small pattern language of its own, and when you need to test a true regular expression against real text, the regex checker runs it live in your browser, with the matches highlighted.
The Verystep Cron Generator & Parser decodes any expression into plain English, validates the ranges, and shows you the next five run times — or build a schedule visually and copy the expression it writes. It’s the fastest way to check the day-of-month trap before your scheduler finds it for you.
Last verified October 24, 2026 — field ranges, operator behaviour and the day-of-month/day-of-week rule cross-checked against the standard crontab (Vixie cron) documentation reflected in the tool below.
Frequently asked questions
What does */15 * * * * mean?
Every 15 minutes, around the clock. The */15 sits in the minute field and means “every 15th minute” — at :00, :15, :30 and :45 — while the stars in the other four fields leave the hour, day, month and weekday unrestricted.
Is Sunday 0 or 7?
Both. In standard cron the day-of-week field accepts 0–7, and 0 and 7 both mean Sunday. Three-letter names (SUN, MON and so on) work in most daemons too. Watch out for tools that only accept 0–6 — there, 7 is an error rather than a second Sunday.
Why did my job run on the 1st and every Monday?
Because both the day-of-month and day-of-week fields were restricted, and standard cron treats that combination as OR, not AND: the job fires when either field matches. 0 9 1 * 1 therefore runs at 09:00 on the 1st of each month and at 09:00 on every Monday. To get AND behaviour, leave one of the two fields as * and filter inside the command.
What is the difference between @daily and 0 0 * * *?
None, on most daemons — @daily is a nickname string for exactly that expression, and @midnight is its alias. The family also includes @hourly (0 * * * *), @weekly (0 0 * * 0), @monthly (0 0 1 * *) and @yearly (0 0 1 1 *). @reboot is the odd one out: it runs at daemon startup, not at a clock time.
Can cron run something every 30 seconds?
Not classic five-field cron — its finest grain is one minute. Some schedulers add a seconds field (making six fields), which changes the dialect entirely, so don’t paste a six-field expression into a five-field crontab. The usual workaround is two entries offset by 30 seconds, with the command itself sleeping for the offset.
More guides

BMI, BMR and TDEE: Three Numbers, Three Different Jobs
One is a ratio, one is an idling cost, one is a daily total. Compare them as if they were the same kind of number and every one of them will mislead you.

Time Zone Math: Why Your Meeting Keeps Landing at 3 A.M.
Nobody on your team is bad at arithmetic. The offset they memorised quietly expired — that's the whole story behind the 3 a.m. meeting.

JSON Without Tears: Structure, and the Five Errors Everyone Makes
One missing comma can break a whole API call — and the error message will blame the wrong line. Here's the tree underneath the text, and the five mistakes that account for nearly every broken file.