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.
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
| Character | Meaning | Example | Interpretation |
|---|---|---|---|
* | Any value | * * * * * | Every minute |
, | List | 1,15 (minute) | At :01 and :15 |
- | Range | 9-17 (hour) | Hours 9 through 17 inclusive |
/ | Step | */5 (minute) | Every 5 minutes |
L | Last | L (day of month) | Last day of month (Quartz only) |
W | Nearest weekday | 15W (day of month) | Nearest weekday to the 15th (Quartz only) |
# | Nth weekday | 2#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:
| Alias | Equivalent | Meaning |
|---|---|---|
@yearly or @annually | 0 0 1 1 * | Once a year, January 1st midnight |
@monthly | 0 0 1 * * | Once a month, first day midnight |
@weekly | 0 0 * * 0 | Once a week, Sunday midnight |
@daily or @midnight | 0 0 * * * | Once a day, midnight |
@hourly | 0 * * * * | Once an hour, at :00 |
@reboot | — | Once 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
| Feature | POSIX/vixie | systemd | Quartz | AWS EventBridge | k8s CronJob |
|---|---|---|---|---|---|
| 5 fields | ✓ | ✓ | 6 fields* | ✓ | ✓ |
L, W, #, ? | ✗ | ✗ | ✓ | Partial | ✗ |
| Seconds field | ✗ | ✓ | ✓ | ✗ | ✗ |
| Timezone config | Per-user CRON_TZ= | Per-unit OnCalendar= | Application config | Supported | 1.27+ |
@reboot | ✓ | N/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
- Cron Pitfalls: Timezones, DST, and Missed Runs — what happens when DST changes your schedule
- IEEE POSIX cron specification
- vixie cron man page
- Quartz Scheduler CronTrigger
Related posts
- Cron Pitfalls: Timezones, DST, and Missed Runs — Your cron job will run twice or skip entirely when DST changes. Here's why, how …
- AWS EventBridge Scheduler — Cron Schedules for Lambda and AWS Services — AWS EventBridge Scheduler runs Lambda functions, ECS tasks, and other AWS target…
- Cron Expression Guide — Syntax, Fields, and Special Characters — Cron expressions schedule recurring jobs using five or six fields: minute, hour,…
Related tool
Build and parse cron expressions with human-readable explanations.
Written by Mian Ali Khalid. Part of the Dev Productivity pillar.