Cron Expression Parser
Read a cron expression in plain English, and see exactly when it will next fire.
minute · hour · day of month · month · day of week. Macros like @daily work too.
This runs
at 03:00, on Monday
Next six runs · UTC
- 2026-08-31 03:00Monday
- 2026-09-07 03:00Monday
- 2026-09-14 03:00Monday
- 2026-09-21 03:00Monday
- 2026-09-28 03:00Monday
- 2026-10-05 03:00Monday
Shown in UTC. A real crontab runs in the server’s timezone, which is where daylight saving causes trouble — see below.
Field by field
- minute
- 00
- hour
- 33
- day of month
- *any
- month
- *any
- day of week
- 11
Worth knowing about this schedule
- Day 31 does not exist in every month, so this simply will not run in those months.
- 29 February exists only in leap years — this runs once every four years.
Five fields, coarsest last
| Position | Field | Range | Notes |
|---|---|---|---|
| 1 | Minute | 0–59 | |
| 2 | Hour | 0–23 | 24-hour clock, server time |
| 3 | Day of month | 1–31 | Days that do not exist are simply skipped |
| 4 | Month | 1–12 | Or JAN–DEC |
| 5 | Day of week | 0–7 | 0 and 7 both mean Sunday. Or SUN–SAT |
Within a field: * is any, , lists values, - gives a range, and / steps. */15 in the minute field is every fifteen minutes; 5/10 means from 5 onwards in tens, which is 5, 15, 25 and so on. Ranges may wrap, so FRI-MON is a valid way to say the weekend plus its edges.
The rule that catches everyone
This is the one thing worth taking away from this page.
When both the day-of-month and day-of-week fields are restricted, cron runs the job when either matches. Not both. It is an OR, and it reads like an AND.
0 0 1 * MON # NOT "the 1st, if it is a Monday"
# Actually: every 1st, AND every MondayThat schedule fires around five times a month. Paste it into the parser above and look at the next run times — the sequence gives it away immediately.
The rule only applies when both fields are restricted. If either is *, the other one simply governs, which is why 0 0 * * MON behaves exactly as expected. The behaviour is in the POSIX specification and every mainstream cron implements it, so it is not a bug you can configure away.
To genuinely require both conditions, restrict one field in the schedule and check the other at the top of the job:
0 0 * * MON [ "$(date +\%d)" = "01" ] && /usr/local/bin/jobDaylight saving, which breaks jobs twice a year
Cron runs in the server’s local timezone unless told otherwise, and local time is not continuous. When clocks go forward, an hour vanishes: a job scheduled for 02:30 may be skipped entirely or fired at 03:30, depending on which cron you have. When clocks go back, 01:30 happens twice, and so may the job.
Modern Vixie cron makes an effort — jobs missed to a forward jump are usually run once afterwards — but the behaviour is not consistent across implementations, and container schedulers vary again.
The durable fix is to run the scheduler in UTC and convert at the edges. Kubernetes CronJobs default to UTC for exactly this reason. If a job must run at a particular local time, accept that it will be an hour out for part of the year, or use a scheduler that understands timezones properly — systemd timers do.
Other reasons a cron job does not run
- The environment is nearly empty. Cron does not source your shell profile, so
PATHis minimal and your virtualenv is not active. Use absolute paths, always. - A bare percent sign. In a crontab,
%means newline.date +%Ysilently breaks; it has to bedate +\%Y. - No trailing newline. Some crons ignore the final line of a crontab that does not end in one.
- Output goes nowhere. Cron mails output to the user, and if mail is not configured it vanishes. Redirect to a log file so failures are visible.
- Overlapping runs. Cron does not wait for the previous run to finish. A five-minute job on a one-minute schedule accumulates until something falls over. Use
flock.
What cron cannot express
The fields are independent, so anything that does not divide evenly into an hour or a day is out of reach. Every 90 minutes cannot be written. Neither can “the last Friday of the month” in standard cron — that is what Quartz’s L and # exist for, and why they are not portable.
If you find yourself fighting the syntax, that is usually the signal to move to a scheduler that thinks in intervals rather than calendar fields. A systemd timer with OnUnitActiveSec=90min says what you meant in one line.
Related
The run times above are in UTC — the timestamp converter will put any of them into your own timezone, or into whatever format your scheduler logs. For matching text rather than scheduling, the regex tester is next door.
Cron questions
Why does my job run on days I did not schedule?
Almost certainly the day-of-month and day-of-week rule. When both fields are restricted, cron runs the job when EITHER matches — not both. So 0 0 1 * MON fires on the first of every month and on every Monday, which is roughly five times a month rather than the once you meant. If either field is *, the rule does not apply and the other simply governs. To require both conditions, restrict one field in cron and check the other inside the job.
What do the five fields mean?
Minute (0–59), hour (0–23), day of month (1–31), month (1–12), day of week (0–7, where both 0 and 7 mean Sunday). Reading left to right gets progressively coarser, which is a useful mnemonic. Some schedulers — Quartz, Spring, and several job runners — prepend a sixth field for seconds; standard crontab has no seconds field at all.
What happens to a cron job during daylight saving?
It depends on the implementation, and this is where scheduled jobs quietly break twice a year. When clocks go forward, an hour does not exist — a 02:30 job may be skipped entirely, or run at 03:30, depending on the cron. When clocks go back, 01:30 happens twice, so the job may run twice. Modern Vixie cron and systemd timers try to be sensible about it; older ones do not. The robust answer is to run scheduled work in UTC and convert at the edges.
What are L, W and # and why are they rejected here?
They are Quartz extensions — L for "last" (last day of month, last Friday), W for the nearest weekday, and # for "the nth weekday of the month". They are genuinely useful and they are not standard cron. crontab, Kubernetes CronJobs and GitHub Actions will all reject or misread them. This parser refuses rather than guessing, because a schedule that silently ignores an L would be wrong in exactly the way nobody checks.
What does @reboot do?
It runs the job once when cron starts, usually at boot. It has no recurring schedule, so there are no next run times to compute — which is why this tool says so instead of showing an empty list. It is worth knowing that @reboot fires when the cron daemon starts, not when the machine finishes booting, so anything depending on the network being up needs its own wait.
How do I run something every 90 minutes?
You cannot express it directly, and this is a genuine limitation rather than a trick you have not learned. Cron fields are independent, so */90 in the minute field is invalid and there is no way to make a schedule that does not divide evenly into an hour or a day. The workarounds are two entries covering the pattern across a day, or — more honestly — a job scheduler that thinks in intervals rather than calendar fields, such as a systemd timer with OnUnitActiveSec.
Last reviewed . Found something out of date? Tell us.
