A cron expression packs a schedule into five short fields. The syntax is old, compact and easy to misread: one misplaced number can mean every minute instead of every hour, or the first of the month instead of every Monday. The reliable way to check a schedule is to read each field separately, then compare the next run times with a calendar. This guide covers the five fields, the supported ranges, lists and steps, the day-of-month and day-of-week quirk, and the limits of a browser-based parser.
Part of the developer toolkit guide. Test an expression in the cron parser, which shows a field breakdown, a plain-language description and the next run times in your browser's local timezone.
The five fields, in order
Every expression has exactly five whitespace-separated fields: minute, hour, day of month, month and day of week. The order never changes. A sixth field is rejected rather than ignored, which is helpful because six-field variants usually add seconds at the front and silently shift every other value.
| Field | Allowed values | Common forms | Example |
|---|---|---|---|
| Minute | 0–59 | *, 5, 1-5, */15, 0,30 | */15 every 15 minutes |
| Hour | 0–23 | *, 9, 9-17, */2 | 9-17 business hours |
| Day of month | 1–31 | *, 1, 15, 1-7 | 1 the first day |
| Month | 1–12 | *, 1, 1-6, */3 | 1-6 January through June |
| Day of week | 0–7 | *, 1, 1-5, 0,6 | 1-5 Monday through Friday |
Day of week uses numbers, not names. Both 0 and 7 mean Sunday, so 0 and 7 are interchangeable for that day, while 1 is Monday and 6 is Saturday. Month names such as JAN and DOW names such as MON are not accepted. Macros such as @daily or @reboot are not accepted either; write the five fields explicitly.
The simplest useful expression is 0 0 * * *: minute 0, hour 0, every day of the month, every month, every day of the week. That is midnight daily, and it is the tool's default, which makes it a useful baseline to edit one field at a time.
Ranges, lists and steps
A single number matches that value. A range such as 9-17 matches every value from 9 through 17 inclusive. A comma-separated list such as 0,30 matches both values, and list entries can themselves be ranges or steps. A step divides a field: */15 in the minute field means every 15 minutes starting at 0, so 0, 15, 30 and 45. A ranged step such as 10-50/10 means 10, 20, 30, 40 and 50.
The tool also accepts a start value with a step, for example 5/10 in the minute field. It treats that as starting at 5 and continuing every 10 minutes through the end of the field, so 5, 15, 25 and so on. Not every scheduler documents this convention the same way, so write the full range explicitly or test the real scheduler.
Values are range-checked. A minute of 60, an hour of 24, a day of month of 0 or a day of week of 8 is rejected with the field name in the error. A step of zero is rejected too. Fix the field rather than assuming the parser will clamp it. Silent clamping is how a schedule ends up running at a time nobody intended.
What the next five runs do and do not show
The parser calculates the next five matching minutes in your browser's local timezone. Each result is also shown with a UTC timestamp, which helps when the person reading the screen and the server running the job are in different places. The list is a simulation, not a guarantee. It starts from the next minute and scans forward roughly 69 days, or 100,000 minutes.
That window explains a common surprise. A monthly schedule may list only two or three upcoming dates because five months do not fit inside 69 days. A yearly schedule or a leap-day expression may list none, even though the expression is valid. The warning about no run times means the scan ended, not necessarily that the schedule is impossible.
The plain-language description is also an approximation, useful for spotting an obvious misread such as “every minute” when you expected “every hour,” but the field breakdown and actual dates are stronger evidence.
Day of month and day of week use OR semantics
This is the rule that causes the most production surprises. When both day of month and day of week are constrained, the parser matches a date if either field matches. The fields are not combined with AND. If day of month is *, only day of week matters; if day of week is *, only day of month matters. When both are restricted, either one can trigger the job.
Example: 0 0 1 * 1 means midnight on the first of the month or on every Monday, not midnight on Mondays that happen to be the first. If you need both conditions, a single cron expression is the wrong tool. Check the date inside the job, use two schedules with a guard, or move the logic into the application.
Day of week 0 and 7 both mean Sunday, including inside ranges and lists. 1-5 is Monday through Friday, 0,6 is Sunday and Saturday, and 5-7 covers Friday through Sunday. Avoid mixing 0 and 7 in the same list; it does not add a second Sunday, but it does make the expression harder to review.
Two worked schedules
Example 1: */15 9-17 * * 1-5. The minute field runs every 15 minutes. The hour field covers 9 through 17, so the first run is 09:00 and the last is 17:45. Day of month is unrestricted, so day of week decides: Monday through Friday only. The result is 36 runs per weekday, four per hour across nine hours. The next-run list should show a weekday sequence and skip the weekend. If it shows Saturday, recheck the day-of-week field.
Example 2: 30 9 1 * *. The minute is 30 and the hour is 9, so the time is 09:30. Day of month is 1 and month is unrestricted, so the job runs on the first day of every month. Day of week is unrestricted, so it does not add a condition. The next-run list may show only two or three entries because of the 69-day scan window. Confirm the first date against a calendar and remember that months have different lengths; the expression handles that automatically.
Run 0 0 1 * 1 as a third check. If you see Mondays that are not the first, the OR behavior is working as designed. That is the moment to decide whether the schedule is what you actually meant.
Timezones, daylight saving and the scheduler
The parser has no timezone selector. It reads the clock in your browser and displays each run in local time and UTC. The server that executes the job may use a different timezone, often UTC. An expression that looks correct on your laptop can run at the wrong local hour in production. Confirm the scheduler's timezone setting before you copy the expression into a deployment.
Daylight saving transitions add another edge. When clocks move forward, a local time may not exist; when they move back, it may occur twice. The browser simulation walks local minutes and can disagree with a scheduler that skips or repeats a wall-clock time. Test schedules that run near a transition, or express critical jobs in UTC and convert only for display. The parser is a reading aid, not a substitute for the scheduler's own documentation.
A review workflow for a new schedule
- Write the five fields in order and say each one aloud: minute, hour, day of month, month, day of week.
- Paste the expression and read the field breakdown before looking at the dates.
- Compare the next runs with a calendar, including the weekday names.
- Check whether both day fields are constrained; if so, confirm the OR behavior is acceptable.
- Confirm the scheduler timezone and test around a daylight saving transition if the job is critical.
- Run the job once manually, then monitor the first scheduled execution.
Keep the expression in version control with a comment describing the intended schedule in plain words. A future reader should not have to decode five fields to learn the schedule, and a change should update the comment in the same commit.
For related debugging work, the JWT expiry guide covers timestamp checks, and the JSON parse error guide covers payloads that jobs consume. Both use the same principle: verify the value, not just the format.