Cron Expression Generator
Build a cron expression from plain controls and preview the next run times.
Cron Expression Generator tool
0–59
0–23
1–31, or *
1–12 or JAN–DEC
0–7 or SUN–SAT
Cron has no timezone of its own — it uses whatever zone the machine running it is set to. Preview the schedule in that zone, not yours.
What this tool does
Cron's five fields are easy to write and hard to be sure about. Set each one, or start from a schedule that already does roughly what you want, and the page shows the two things that tell you whether the expression is right: a sentence describing it and the next ten moments it would fire. If the tenth run is not where you expected, the expression is wrong, whatever it looks like.
The optional seconds and year fields are for Quartz, Spring's @Scheduled and
node-cron. Leave them off for anything going into a real crontab, a Kubernetes
CronJob or a GitHub Actions schedule, none of which accept them.
Common uses
- Writing the schedule for a nightly backup, a cache warm or a report job.
-
Spreading load: turning "every hour" into
17 * * * *so a fleet of machines does not all fire on the minute. -
Producing the
spec.schedulevalue for a Kubernetes CronJob and confirming the cadence before applying it. - Checking that a business-hours job really does skip the weekend.
A short example
Quarter-hourly during office hours on weekdays:
*/15 9-17 * * 1-5
Every 15 minutes between 09:00 and 17:59, on Monday through Friday.
That is 36 runs a day, 180 a week, and the last one is at 17:45.
Note that 9-17 includes hour 17 in full, so the schedule runs until 17:45,
not 17:00. For a last run at 17:00 exactly, add 0 17 * * 1-5 as a second
entry rather than trying to express it in one.
Writing each field
Every field takes the same four constructions, which combine freely with commas:
a single value (5), a range (9-17), a step
(*/15, or 9-17/2 for every second hour inside a range) and a list
(1,15,30). Month and day-of-week additionally accept three-letter names, so
MON-FRI and 1-5 are identical, as are JAN and
1.
Steps are the most misread. */20 in the minute field means minutes 0, 20 and
40 — it counts from the start of the range, not from the moment you install the job, so it
does not mean "every twenty minutes from now". A step larger than the field's range can
only ever match its first value, so this tool refuses it. Sunday is both 0
and 7; February 30th is accepted by the syntax and simply never fires, which
the tool warns about.
Worth knowing
A crontab entry inherits a minimal environment: typically PATH=/usr/bin:/bin,
no shell profile, and HOME from the passwd file. Commands that work in your
terminal frequently fail under cron for this reason alone, so use absolute paths. Percent
signs are special in a crontab — they become newlines fed to the command's standard input
unless escaped as \%, which catches out anyone using
date +%Y-%m-%d in a job. And output is not discarded: anything written to
stdout or stderr is mailed to the owner, or silently dropped if no mailer exists.
Frequently asked questions
Why does my job run twice, or not at all, twice a year?
Cron fires against the host’s local clock, so a daylight saving shift drags the schedule with it. Vixie cron patches half the problem: an entry with a fixed hour is still run once when the clock springs forward and is not repeated when it falls back. A wildcard schedule like */10 * * * * gets no such help and genuinely runs twice through the repeated hour. Run the host on UTC, or use the Kubernetes CronJob timeZone field, stable since 1.27.
What do @daily and @reboot actually mean?
They are crontab shorthands rather than five-field expressions: @hourly is 0 * * * *, @daily and @midnight are 0 0 * * *, @weekly is 0 0 * * 0, @monthly is 0 0 1 * *, @yearly is 0 0 1 1 *. @reboot is the odd one, firing when the cron daemon starts rather than when the machine boots. GitHub Actions rejects all of them and Quartz has no equivalent, so they do not travel.
Why is my GitHub Actions schedule running late?
It is best-effort by design. Scheduled workflows are evaluated on GitHub’s UTC clock and queued behind everything else on the platform, so delays of several minutes are routine and runs at the top of the hour — when every repository asks at once — can be dropped altogether. Choosing 17 * * * * over 0 * * * * measurably helps. A schedule is also disabled automatically after 60 days without a commit.