X Xerobit

Cron Expression Syntax, Explained by Field

Five fields, nine special characters, a dozen edge cases. A complete reference for cron syntax — minute, hour, day, month, weekday — with annotated real-world examples.

Mian Ali Khalid · · 7 min read
Use the tool
Cron Builder
Build and parse cron expressions with human-readable explanations.
Open Cron Builder →

Cron has been running scheduled jobs since 1975. The expression format has barely changed. Yet “cron syntax” is one of the most-Googled things by engineers who write it a dozen times a year — because the field order is counterintuitive, the edge cases are many, and the difference between implementations is silently wrong.

This is the reference you can stop Googling.

The Cron Builder on this site parses any expression and shows the next 10 scheduled runs, the human-readable explanation, and flags implementation differences. Use it while reading this.

The five-field format

┌───────────── minute        (0–59)
│ ┌─────────── hour          (0–23)
│ │ ┌───────── day of month  (1–31)
│ │ │ ┌─────── month         (1–12 or JAN–DEC)
│ │ │ │ ┌───── day of week   (0–7 or SUN–SAT, where 0 and 7 = Sunday)
│ │ │ │ │
* * * * *

A cron expression is executed when all five fields match the current time — with one notable exception for day-of-month / day-of-week (covered below).

Field reference

Minute (0–59)

0      → exactly on the hour
30     → at :30 past each hour
*/15   → every 15 minutes (0, 15, 30, 45)
5,20   → at :05 and :20 past each hour

Hour (0–23)

0      → midnight (12:00 AM)
9-17   → every hour from 9 AM to 5 PM
*/6    → every 6 hours (0, 6, 12, 18)

Day of month (1–31)

1      → first of the month
L      → last day of the month (Quartz, not POSIX/vixie cron)
15,30  → 15th and 30th

Note: months have different lengths. A job scheduled on the 31st won’t run in April, June, September, or November. A job on the 29th won’t run every February. This is almost always a surprise when it first matters.

Month (1–12 or JAN–DEC)

1      → January (not 0)
*/3    → every quarter (January, April, July, October)
JAN,JUL → January and July (name aliases are case-insensitive in most impls)

Month is 1-indexed. This trips up programmers who are used to 0-indexed months from JavaScript’s Date.getMonth().

Day of week (0–7 or SUN–SAT)

0      → Sunday (POSIX/vixie cron)
7      → Sunday (also valid in vixie cron — 0 and 7 are both Sunday)
1      → Monday
5      → Friday
MON-FRI → weekdays

The 0-or-7-for-Sunday quirk exists because the original POSIX spec used 0, but some early implementations also accepted 7, and vixie cron preserved both. Most modern cron implementations accept both. When in doubt, use the named form (SUN) to avoid ambiguity.

Special characters

CharacterMeaningExampleInterpretation
*Any value* * * * *Every minute
,List1,15 (minute)At :01 and :15
-Range9-17 (hour)Hours 9 through 17 inclusive
/Step*/5 (minute)Every 5 minutes
LLastL (day of month)Last day of month (Quartz only)
WNearest weekday15W (day of month)Nearest weekday to the 15th (Quartz only)
#Nth weekday2#3 (day of week)Third Monday (Quartz only)
?No specific value?Used in Quartz for day-of-month or day-of-week when the other is set

The L, W, #, and ? characters are Quartz Scheduler extensions. They do not work in POSIX cron, vixie cron, or most Linux cron implementations. If you’re writing crontab entries for a Linux server, stick to * , - /.

The day-of-month / day-of-week OR semantics

This is the most important edge case in all of cron:

If both day-of-month and day-of-week are set (not *), most cron implementations run the job when EITHER condition is true — not both.

0 0 15 * 1    → runs on the 15th of every month
               AND on every Monday, midnight
               (NOT "only on Mondays that are the 15th")

This surprises virtually everyone who writes it for the first time. The intent is usually “the 15th of every month” but the result is “the 15th OR every Monday.”

If you want “the 15th of every month only,” use:

0 0 15 * *

If you want “the first Monday of each month,” you need a workaround — cron doesn’t express this natively. Common approaches:

  • Use 0 0 1-7 * 1 (any day from 1–7 that is also Monday) — this relies on the OR semantics to coincidentally work, but only because 1-7 covers exactly the first week
  • Use a wrapper script that checks the date inside the job

The Quartz scheduler’s ? character exists specifically to handle this: 0 0 15 * ? means “15th of every month, day of week not specified” — removing the ambiguity.

Common expression examples

# Every minute
* * * * *

# Every hour, at :00
0 * * * *

# Every day at midnight
0 0 * * *

# Every day at 9 AM
0 9 * * *

# Every weekday at 8 AM
0 8 * * 1-5

# Every Sunday at 2 AM (for maintenance)
0 2 * * 0

# Every 15 minutes
*/15 * * * *

# Every 30 minutes, during business hours weekdays
*/30 9-17 * * 1-5

# First day of every month at midnight
0 0 1 * *

# Every quarter (Jan, Apr, Jul, Oct) on the 1st at midnight
0 0 1 1,4,7,10 *

# Twice a day, at 9 AM and 9 PM
0 9,21 * * *

@shorthand aliases

Most cron implementations support shorthand aliases that expand to standard expressions:

AliasEquivalentMeaning
@yearly or @annually0 0 1 1 *Once a year, January 1st midnight
@monthly0 0 1 * *Once a month, first day midnight
@weekly0 0 * * 0Once a week, Sunday midnight
@daily or @midnight0 0 * * *Once a day, midnight
@hourly0 * * * *Once an hour, at :00
@rebootOnce at system startup

These are vixie cron extensions. They are not part of POSIX cron. They work on most Linux systems (systemd cron, Debian/Ubuntu cron, macOS cron) but not in AWS EventBridge, Kubernetes CronJobs, or Quartz.

Implementation differences to know

FeaturePOSIX/vixiesystemdQuartzAWS EventBridgek8s CronJob
5 fields6 fields*
L, W, #, ?Partial
Seconds field
Timezone configPer-user CRON_TZ=Per-unit OnCalendar=Application configSupported1.27+
@rebootN/A

*Quartz adds a seconds field at position 0, shifting all others right. Quartz expressions look like 0 0 9 * * ? (second minute hour dom month dow).

Validating your expression

Paste your expression into the Cron Builder. It shows:

  • The human-readable explanation of the schedule
  • The next 10 scheduled run times
  • Which implementation quirks apply

Do this before deploying. A wrong cron expression runs silently — you won’t know until the job misses or double-fires.

Further reading


Related posts

Related tool

Cron Builder

Build and parse cron expressions with human-readable explanations.

Written by Mian Ali Khalid. Part of the Dev Productivity pillar.