How it works
A standard cron expression is five fields separated by whitespace. Read left to right, they answer: at which minute, in which hour, on which day of the month, in which month, and on which day of the week should this job run.
* * * * *
| | | | |
| | | | +-- day of week (0-7, 0 and 7 both mean Sunday)
| | | +----- month (1-12 or JAN-DEC)
| | +-------- day of month (1-31)
| +----------- hour (0-23)
+-------------- minute (0-59)
A job runs at a given minute only when every field matches that moment — with one famous
exception covered below. The fields are not a countdown or an interval; they are a filter applied to the
clock. That is why */20 * * * * fires at :00, :20 and :40 of every hour rather than
"every 20 minutes starting whenever you saved the crontab".
The five fields and their allowed values
| Position | Field | Allowed values | Notes |
|---|---|---|---|
| 1 | Minute | 0-59 | Smallest unit standard cron understands. |
| 2 | Hour | 0-23 | 24-hour clock. Midnight is 0, not 24. |
| 3 | Day of month | 1-31 | Days that do not exist in a month are simply skipped. |
| 4 | Month | 1-12 | Names also work: JAN … DEC. |
| 5 | Day of week | 0-7 | 0 and 7 are both Sunday. Names work: SUN … SAT. |
Operators you can use in any field
| Operator | Example | Meaning |
|---|---|---|
| * | * * * * * | Every value of that field. |
| , | 0,15,30,45 | A list of specific values. |
| - | 9-17 | An inclusive range. |
| / | */5 | A step across the whole field: 0, 5, 10, … |
| - | 10-30/5 | A step across a range: 10, 15, 20, 25, 30. |
| @ | @daily | Shorthand for 0 0 * * *. Also @hourly, @weekly, @monthly, @yearly. |
How the next run times are calculated
This tool does not guess. It parses each field into the exact set of values it matches, then walks forward one calendar day at a time from the current minute, testing the month and day rules for each day and expanding the matching hours and minutes in order. That approach handles leap years, months of different lengths and unsatisfiable expressions correctly.
The search stops after roughly four years. If an expression such as 0 0 30 2 *
(30 February) can never match, the tool reports that there are no upcoming runs instead of freezing.
Local times are rendered using your device time zone; UTC is shown alongside because most servers and
most hosted schedulers run on UTC.
Worked examples
1. Quarter-hourly during office hours on weekdays
*/15 9-17 * * 1-5
Minute steps by 15 (:00, :15, :30, :45), the hour is restricted to the range 9 through 17, and the day of week is Monday through Friday. That is 36 runs per working day.
The trap here is the hour range. 9-17 includes hour 17 in its entirety, so the
last run of the day is 17:45, not 17:00. If you want the final run to be exactly 17:00, use
0 17 * * 1-5 as a separate line, or narrow the range to 9-16 and accept that
nothing fires after 16:45.
2. Monthly versus quarterly
0 0 1 * * runs at midnight on the 1st of every month. 0 0 1 */3 * runs at midnight on the 1st of every third month.
Steps in the month field are anchored to the start of the field, not to today. */3
expands to months 1, 4, 7 and 10 — January, April, July and October — regardless of when you
install the crontab. If your fiscal quarters start in February you need 2,5,8,11 instead.
The same anchoring applies everywhere: */7 in the day-of-month field means days 1, 8, 15,
22 and 29, which is not a stable weekly cadence because the count restarts each month.
3. The day-of-month / day-of-week OR rule
0 12 13 * 5
Read literally this looks like "noon on the 13th, but only if it is a Friday". It is not. When both the day-of-month field and the day-of-week field are restricted, cron combines them with OR: the job runs at noon on the 13th of any month and on every Friday. In a typical year that is more than sixty runs, not one or two.
To actually get "Friday the 13th only" you cannot use a single standard cron expression. The usual
workaround is to schedule 0 12 13 * * and start the command with a guard such as
[ "$(date +\%u)" = 5 ] && /path/to/job. Note the escaped percent sign: in a crontab
an unescaped % is turned into a newline and everything after it is fed to the command
on standard input.
4. The step-wraparound trap
*/45 * * * *
This does not run every 45 minutes. The minute field only counts from 0 to 59, so */45
expands to minutes 0 and 45. The job fires at 00:00 and 00:45, then again at 01:00 — a gap of
fifteen minutes, not forty-five.
Any interval that does not divide evenly into 60 minutes (or into 24 hours) will behave like this.
For a genuine 90-minute cadence you need to enumerate the times yourself, for example
0 0,3,6,9,12,15,18,21 * * * combined with 30 1,4,7,10,13,16,19,22 * * *,
or move the interval logic into the job itself.
5. A nightly job and daylight saving time
0 2 * * *
On a server set to UTC this is unremarkable: one run per day, 24 hours apart, forever. On a server set to a zone that observes daylight saving time, 02:00 is exactly the hour that disappears in spring and repeats in autumn. Depending on the cron implementation the job may be skipped on the spring transition day, or run twice on the autumn one.
The reliable fix is to run schedulers in UTC and convert at the edges, or to pick an hour that no common time zone shifts across, such as 04:00 or 05:00. Vixie cron on Linux has special handling for jobs in the skipped hour; container schedulers and cloud cron services usually do not.
Frequently asked questions
What do the five fields actually mean?
In order: minute (0–59), hour (0–23), day of month (1–31), month (1–12) and day of week (0–7). A run happens at any minute where all the fields are satisfied. There is no seconds field in standard cron and no year field — those belong to Quartz and Spring, which use six or seven fields.
If I restrict both day-of-month and day-of-week, are they AND-ed or OR-ed?
OR-ed. This is the single most misunderstood rule in cron. When either of the two day fields
is *, the other one simply filters the days as you would expect. But when
neither is *, the daemon runs the job on any day that matches the day-of-month
field or the day-of-week field.
So 0 0 1 * 1 is not "the 1st, if it is a Monday". It is "the 1st of every month, plus
every Monday". If you need both conditions to hold, use a wildcard in one field and test the other
condition inside the command.
One subtlety: a field beginning with * counts as unrestricted for the purposes of this
rule, including */2. That matches the behaviour of Vixie cron, the implementation behind
cron on most Linux distributions, and this tool follows it.
Is Sunday 0 or 7?
Both. Standard cron accepts 0 through 7 in the day-of-week field and treats 0
and 7 as Sunday, which makes ranges such as 5-7 (Friday, Saturday, Sunday) expressible.
Monday is always 1. Some non-cron schedulers — notably Quartz — use 1 for Sunday and 7 for
Saturday, so double-check before copying an expression between systems.
What is the difference between 5 * * * * and */5 * * * *?
5 * * * * runs once an hour, at five minutes past the hour —
24 runs a day. */5 * * * * runs every five minutes, at :00, :05, :10 and so on —
288 runs a day. The slash introduces a step; a bare number is a single value. Mixing them up is the
most common cause of a job that runs 12 times more often than intended.
Which time zone does cron use?
The system time zone of the machine running the daemon, unless the crontab sets
CRON_TZ (Vixie cron) or TZ at the top of the file. Managed schedulers vary:
GitHub Actions schedule triggers are always UTC, Kubernetes CronJob defaults to the
controller time zone but accepts a timeZone field, and most cloud schedulers let you pick
a zone explicitly. The tool above shows both your local time and UTC so you can check which one your
expression really targets.
What happens to my job during a daylight saving change?
It depends on the implementation. Vixie cron treats jobs scheduled in the hour
that is skipped in spring as due immediately after the jump, and suppresses duplicate runs in the
repeated autumn hour for fixed-time jobs. Interval jobs such as */10 * * * * simply follow
the wall clock, so you get one short hour and one long one. Container and cloud schedulers frequently
have no special handling at all. Running everything in UTC removes the problem entirely.
Can I schedule something every 30 seconds?
Not with standard five-field cron — one minute is the finest granularity.
The classic workaround is a single crontab line that runs the command, sleeps and runs it again:
* * * * * /path/to/job; sleep 30; /path/to/job. Cleaner alternatives are systemd timers
with OnUnitActiveSec=30s, or a long-running process with its own internal loop. Quartz and
Spring cron expressions do have a seconds field, but they are not compatible with system cron.
Why does my cron job never run?
In rough order of likelihood:
- Environment. Cron runs with a minimal environment and a short
PATH(often just/usr/bin:/bin). Use absolute paths for every binary and every file. - Unescaped percent signs. In a crontab,
%becomes a newline. Anydate +%Ymust be writtendate +\%Y. - No trailing newline. Some cron implementations silently ignore the last line of a crontab file if it does not end with a newline.
- Wrong crontab. A job added with
crontab -eas your user is not the same as/etc/crontabor a file in/etc/cron.d, and the system files need an extra user column. - Output going nowhere. Redirect to a file
(
>> /var/log/myjob.log 2>&1) so failures are visible, then checkjournalctl -u cronor/var/log/syslog.
Does this tool support Quartz, Spring or six-field expressions?
No. It deliberately implements standard five-field Vixie/POSIX cron, which is
what Linux crontabs, Kubernetes CronJob, GitHub Actions and most cloud schedulers use. If you paste a
six- or seven-field expression, or one containing Quartz specials such as L,
W or #, you will get an explicit error rather than a wrong answer. The
Quartz-style ? placeholder is accepted and treated as *, since that is what
it means in practice.
What does "no upcoming runs" mean?
The expression is syntactically valid but can never match a real date within the
four-year search window. The classic case is 0 0 30 2 * — there is no 30 February.
0 0 31 4 * is another: April has 30 days. Note that a valid but rare expression such as
0 0 29 2 * (29 February) does have runs, just infrequent ones, so you may see fewer than
five results rather than none.
Are my expressions sent anywhere?
No. The parser, the explainer and the next-run calculation are all plain JavaScript running in your browser. There is no API call and no server-side processing, so the expression you paste in is never uploaded or logged anywhere. You can save the page and use it offline, and the tool will keep working.
How do I write a schedule for the last day of the month?
Standard cron cannot express it — there is no L token. The
usual pattern is to run the job on the 28th through the 31st and let the command decide:
0 3 28-31 * * [ "$(date -d tomorrow +\%d)" = "01" ] && /path/to/job. The guard is
true only on whichever of those days is actually the last one. Quartz users can write
0 0 3 L * ? instead.
Use cases and limitations
What people schedule with cron
- Backups and snapshots. Nightly database dumps, weekly full backups, monthly archive rotation. Usually run in the small hours in the server time zone.
- Certificate renewal. ACME clients typically want a twice-daily check at a random minute so that renewals do not stampede the certificate authority on the hour.
- Cache warming and index rebuilds. Frequent short jobs, often
*/5or*/15, restricted to business hours to save resources overnight. - Report generation and billing runs. Monthly or quarterly schedules pinned to a
specific day of the month, such as
0 6 1 * *. - CI and repository automation. GitHub Actions, GitLab CI and most build systems
accept five-field cron in a
scheduleblock, always interpreted as UTC. - Kubernetes CronJob. Same syntax, plus a
timeZonefield and concurrency policies that plain cron does not have.
What cron cannot do
- Sub-minute scheduling. One minute is the floor.
- Relative intervals. "Every 90 minutes" and "every 45 minutes" cannot be expressed because steps are anchored to the start of the field and reset at its boundary.
- Last day of month, nth weekday, business-day arithmetic. These require Quartz syntax or a guard inside the command.
- Catch-up after downtime. If the machine is asleep or the daemon is stopped, the
run is simply missed.
anacronexists for exactly this reason on laptops and workstations. - Overlap protection. If a run takes longer than the interval, cron happily starts
another one. Wrap long jobs in
flockor an equivalent lock.
Limitations of this tool
The run times shown here are a simulation of what a correct cron implementation would do, computed against your browser clock and time zone database. They are a planning aid, not a guarantee: your daemon's time zone, its daylight-saving behaviour, missed runs during downtime and any scheduler-specific extensions can all shift reality. The search window is capped at about four years, so very sparse schedules may return fewer than five results. Quartz, Spring and other non-standard dialects are intentionally out of scope.