What a cron expression actually says
A cron expression is a filter over the clock, not a countdown. Every minute the scheduler wakes up, looks at the current time, and asks each column whether it matches; when they all agree, the job runs.
0 3 * * 1 → 03:00 every Monday — five columns: minute, hour, day of month, month, day of week
That framing explains the two things people find surprising. Nothing anywhere records when the job last ran or when it is due next, so a schedule has no start date — changing the file changes the very next run — and no duration, so a job that takes eleven minutes on a five-minute schedule will start again before the last copy has finished. This tool reads the expression the way the scheduler does and lists the instants it produces; everything happens in your browser, which matters more than usual because schedules tend to sit in the same files as credentials.
The columns
Fields are separated by whitespace and read left to right. The five-field form is what crontab has used since the 1970s; the seconds column in front and the year column behind are later additions that Quartz, Spring and node-cron accept.
| Column | Values | Special | Notes |
|---|---|---|---|
| Second | 0-59 | * , - / | Only present in the six- and seven-field forms. A five-field line fires on the zeroth second. |
| Minute | 0-59 | * , - / | The first column of a classic crontab line. |
| Hour | 0-23 | * , - / | Always a 24-hour clock. There is no AM or PM anywhere in cron. |
| Day of month | 1-31 | * , - / ? L W | Days that a month does not have are simply never matched — 31 in February costs you nothing, it just does not fire. |
| Month | 1-12, JAN-DEC | * , - / | Accepts JAN through DEC, case-insensitively, as well as 1 through 12. |
| Day of week | 0-7, SUN-SAT | * , - / ? L # | Accepts SUN through SAT. The numbering depends on the dialect, which is what the Weekday numbering control is for. |
| Year | 1970-2199 | * , - / | Quartz only, and rarely useful outside a one-off migration job. |
Which columns you get depends on how many you write. Five fields start at the minute. Six put seconds in front. Seven put seconds in front and a year behind. Anything else is rejected rather than guessed at, because a six-field line read as five would silently move every run by a whole column.
Syntax, one character at a time
Each column is a comma-separated list of terms, and each term is one of the shapes below. The last six are Quartz extensions: plain crontab does not know them, so a line using them will not load into a Unix crontab.
| Term | Means | Where |
|---|---|---|
| * | Every value the column can hold. | Every column. |
| 1,15,30 | Each listed value, merged and deduplicated. | Every column. |
| MON-FRI | Every value from the first to the last, inclusive. | Every column. Names may be used where the column accepts them. |
| */15 | Every nth value, counting from the bottom of the column. | Every column. |
| 0-30/10 | Every nth value inside a range — 0, 10, 20 and 30 here. | Every column. |
| 5/15 | Every nth value from a starting point to the top of the column — 5, 20, 35 and 50 here. | Every column. A Quartz spelling that most crontabs also accept. |
| ? | No value of its own; leaves the decision to the other day column. | Day of month and day of week only. |
| L | The last day of the month, whether that is the 28th or the 31st. | Day of month only. |
| L-3 | That many days before the last day of the month. | Day of month only. |
| LW | The last Monday-to-Friday day of the month. | Day of month only. |
| 15W | The weekday nearest that date, without crossing into another month. | Day of month only. |
| FRIL | The last Friday of the month. Any weekday number or name works. | Day of week only. |
| FRI#3 | The third Friday of the month. The ordinal runs from 1 to 5. | Day of week only. |
Two things are worth remembering. A range never wraps: FRI-MON is rejected rather than read as the weekend, so write FRI,SAT,SUN,MON instead. And a step counts from the bottom of its column, not from now — */20 in the minute column fires at :00, :20 and :40 whatever time you save the file.
The @ macros
Vixie cron accepts eight shorthands in place of five fields. They are expanded before anything else, so the breakdown above shows the columns they stand for rather than the macro itself.
| Macro | Same as | Notes |
|---|---|---|
| @yearly | 0 0 1 1 * | @annually is a second spelling of the same thing. |
| @monthly | 0 0 1 * * | Midnight on the first of every month. |
| @weekly | 0 0 * * 0 | Midnight on Sunday, whichever numbering the dialect uses. |
| @daily | 0 0 * * * | @midnight is a second spelling of the same thing. |
| @hourly | 0 * * * * | On the hour, every hour. |
| @reboot | — | Not a clock schedule at all. It fires once, when the daemon starts, so it has no next run to show — and it does not fire again if you merely reload the crontab. |
The controls
Three settings change what the run list means. None of them change how the expression is read except the second, which changes what a bare weekday digit stands for.
| Control | What it does | Reach for it when |
|---|---|---|
| Time zone | Works out the run times against that zone's wall clock, including its daylight saving rules. | Always — set it to whatever the machine running the job is set to, which is very often UTC. |
| Weekday numbering | Decides whether a bare digit in the day-of-week column counts Sunday as 0 or as 1. | The expression came from Quartz, Spring or Jenkins and uses digits rather than names. |
| Runs to preview | How many upcoming run times to list, from 1 to 25. | You need to see a whole week of a daily job, or just confirm the next one. |
The zone is a property of the machine, not of the expression. A crontab line carries no zone at all; it fires on whatever the host's clock says. Kubernetes CronJob and Quartz can each be told a zone explicitly, and this control is how you model that.
Weekday numbering only matters for digits. SUN, MON and the rest name the same day under both dialects, which is the strongest argument for always writing names — a schedule that reads the same everywhere cannot be misread when it moves between systems.
Five ways a schedule surprises people
When both day columns are restricted, cron fires when either matches, not when both do. 0 0 1 * MON runs on the first of the month and on every Monday — four or five times as often as most people expect. If only one of the two columns is a star, the columns are intersected as usual, which is why the rule is so easy to miss.
A step counts from the bottom of its column, not from the moment you saved the file. */7 in the hour column fires at 00:00, 07:00, 14:00 and 21:00, then jumps back to 00:00 — the gap across midnight is three hours, not seven. Steps that do not divide evenly into their column always have a short interval at the wrap.
In a zone that observes daylight saving, an hour goes missing every spring. A job at 02:30 does not run at all on that day, because 02:30 never happens. This tool counts those and tells you underneath the run list rather than quietly listing 03:30 instead.
In the autumn the same hour happens twice. Most schedulers fire once, on the first pass, which is what the run list shows — but implementations genuinely differ here, and a job that must run exactly once should not be scheduled inside the repeated hour at all.
Cron never waits for the previous run to finish. A five-minute schedule around a job that sometimes takes seven minutes will eventually run two copies at once. Guard it with a lock file, flock, or whatever your runner offers — the schedule cannot do it for you.
When to reach for this
Reviewing a pull request that touches a crontab, a Kubernetes CronJob, or a Quartz trigger. Reading the sentence and the next five runs takes a few seconds and catches the off-by-one-column mistakes that a diff makes almost invisible.
Working out why a job ran at the wrong time. Set the zone to the server's, and the run list will usually show the answer straight away — a UTC box firing a report at what the reporter thought was local midnight is the single most common cause.
Writing a new schedule from scratch. Start from an example, adjust one column at a time, and watch the sentence and the run times change as you type.
Everything runs in your browser. The expression is never uploaded, which matters because schedules usually live beside secrets, hostnames, and internal paths.
Frequently asked questions
Quartz, as a question mark.