A cron expression is a string of five space-separated fields that tells a scheduler exactly when to run a task: minute hour day-of-month month day-of-week. Each field accepts a number, a wildcard (* for "every value"), or a pattern built from special characters — so 30 14 * * 1-5 means "at 2:30 PM, Monday through Friday," and */15 * * * * means "every 15 minutes." Cron expressions began in Unix but are now the near-universal way to describe recurring schedules, from Linux crontab and Kubernetes CronJobs to AWS EventBridge, GitHub Actions, and language libraries in Node.js, Python, and Java.
Looking up one specific expression? Jump straight to the cron expression meanings table, which gives the plain-English meaning and the exact run times for every common pattern. The rest of this guide covers the range of every field, what each special character does, the platform variations that silently break a copy-pasted expression, and the traps that make a schedule fire when you did not expect it.
The five fields at a glance
Every standard cron expression is five fields, read left to right, moving from the smallest time unit to the weekday. An asterisk in any field means "every value."
Written as a pattern, the structure is:
<minute> <hour> <day-of-month> <month> <day-of-week>
And the classic ASCII diagram that ships in most crontab files:
┌───────────── minute (0 - 59)
│ ┌───────────── hour (0 - 23)
│ │ ┌───────────── day of month (1 - 31)
│ │ │ ┌───────────── month (1 - 12)
│ │ │ │ ┌───────────── day of week (0 - 7) (0 or 7 is Sunday)
│ │ │ │ │
* * * * *
If you want to practice turning expressions like this into plain English, our companion guide on how to read cron expressions walks through the field-by-field method step by step.
Field reference: exact ranges and allowed values
This is the table to bookmark. It covers every field, its numeric range, whether names are accepted, and the gotchas.
| Field | Position | Allowed values | Names accepted? | Notes |
|---|---|---|---|---|
| Minute | 1st | 0-59 | No | 0-based |
| Hour | 2nd | 0-23 | No | 24-hour clock; 0 = midnight |
| Day of month | 3rd | 1-31 | No | 1-based; invalid dates (e.g. Feb 31) never fire |
| Month | 4th | 1-12 | JAN-DEC | 1-based (1 = January) |
| Day of week | 5th | 0-7 | SUN-SAT | 0 and 7 both = Sunday |
Two traps live in this table. Minutes and hours are 0-based, but day-of-month and month are 1-based — there is no minute 60 and no month 0. And Sunday is both 0 and 7, a historical quirk so that ranges like MON-SUN (1-7) still work.
Special characters: the four you must know
The power of cron comes from four special characters that work inside any field. Master these and you can express almost any schedule.
| Character | Name | Meaning | Example | Reads as |
|---|---|---|---|---|
* | Asterisk | Every value in the field | * * * * * | Every minute |
, | Comma | A list of specific values | 0 9,12,15 * * * | 9 AM, noon, and 3 PM |
- | Hyphen | An inclusive range | 0 9-17 * * * | Every hour, 9 AM to 5 PM |
/ | Slash | A step ("every N") | */15 * * * * | Every 15 minutes |
You can combine them freely. */5 9-17 * * 1-5 uses a step, a range, and a range in one expression — "every 5 minutes, between 9 AM and 5 PM, Monday through Friday." Our deep dive on what */5 really means unpacks how step values interact with ranges, which is where most people's mental model breaks.
The question mark (?)
Some schedulers — notably Quartz and AWS — add a ? for the day-of-month or day-of-week field to mean "no specific value." It resolves the ambiguity of specifying one but not the other:
0 0 1 * ? // 1st of every month (day of week doesn't matter)
0 0 ? * MON // Every Monday (day of month doesn't matter)
Standard Vixie cron on Linux does not support ? — using it there is an error. This is a common source of "works on my scheduler, breaks on the server" bugs.
What does each cron expression mean?
A cron expression means "run this task at every point in time that matches all five fields." The tables below give the plain-English meaning of every common expression, along with exactly when it fires and how often. All meanings assume standard five-field Vixie cron on Linux, running in the server's local time zone.
Every N minutes
| Expression | What it means | When it runs |
|---|---|---|
* * * * * | * * * * * runs every minute of every hour, every day. | Fires at every minute — 1,440 times a day. |
*/2 * * * * | */2 * * * * runs every 2 minutes. | Fires at :00, :02, :04 … :58 — 720 times a day. |
*/5 * * * * | */5 * * * * runs every 5 minutes. | Fires at :00, :05, :10 … :55 — 288 times a day. |
*/10 * * * * | */10 * * * * runs every 10 minutes. | Fires at :00, :10, :20, :30, :40, :50 — 144 times a day. |
*/15 * * * * | */15 * * * * runs every 15 minutes, four times an hour. | Fires at :00, :15, :30 and :45 — 96 times a day. |
*/30 * * * * | */30 * * * * runs every 30 minutes, twice an hour. | Fires at :00 and :30 — 48 times a day. |
15 * * * * | 15 * * * * runs once an hour, at 15 minutes past the hour. | Fires at 00:15, 01:15, 02:15 and so on — 24 times a day. |
Hourly
| Expression | What it means | When it runs |
|---|---|---|
0 * * * * | 0 * * * * runs once an hour, at the top of every hour. | Fires at 00:00, 01:00, 02:00 … 23:00 — 24 times a day. |
0 */2 * * * | 0 */2 * * * runs every 2 hours, on the hour. | Fires at 00:00, 02:00, 04:00 … 22:00 — 12 times a day. |
0 */6 * * * | 0 */6 * * * runs every 6 hours, on the hour. | Fires at 00:00, 06:00, 12:00 and 18:00 — 4 times a day. |
0 9-17 * * * | 0 9-17 * * * runs once an hour from 9 AM through 5 PM. | Fires at 09:00, 10:00 … 17:00 — 9 times a day. |
* */2 * * * | * */2 * * * runs every minute during every second hour — not every two hours. | Fires 60 times inside each of the hours 0, 2, 4 … 22 — 720 times a day. |
That last row is the most common misreading in cron. The step belongs to the field it appears in: */2 in the hour field selects the hours, while the * still in the minute field means every minute within them. If you want "every 2 hours," the minute field must be pinned: 0 */2 * * *.
Daily
| Expression | What it means | When it runs |
|---|---|---|
0 0 * * * | 0 0 * * * runs once a day at midnight. | Fires at 00:00 every day. |
0 2 * * * | 0 2 * * * runs once a day at 2:00 AM — the usual backup window. | Fires at 02:00 every day. |
30 2 * * * | 30 2 * * * runs once a day at 2:30 AM. | Fires at 02:30 every day. |
0 12 * * * | 0 12 * * * runs once a day at noon. | Fires at 12:00 every day. |
0 0,12 * * * | 0 0,12 * * * runs twice a day, at midnight and noon. | Fires at 00:00 and 12:00. |
30 14 * * * | 30 14 * * * runs once a day at 2:30 PM. | Fires at 14:30 every day. |
Weekdays and weekends
Day-of-week numbering runs 0-7, where 0 and 7 both mean Sunday, 1 is Monday and 6 is Saturday.
| Expression | What it means | When it runs |
|---|---|---|
0 0 * * 1-5 | 0 0 * * 1-5 runs at midnight Monday through Friday. | Fires at 00:00 on weekdays only — 5 times a week. |
30 14 * * 1-5 | 30 14 * * 1-5 runs at 2:30 PM Monday through Friday. | Fires at 14:30 on weekdays only. |
0 9-17 * * 1-5 | 0 9-17 * * 1-5 runs hourly from 9 AM to 5 PM on weekdays. | Fires 9 times a day, Monday through Friday. |
*/15 9-17 * * 1-5 | */15 9-17 * * 1-5 runs every 15 minutes during business hours on weekdays. | Fires 36 times a day, Monday through Friday. |
0 0 * * 0,6 | 0 0 * * 0,6 runs at midnight on Saturday and Sunday. | Fires at 00:00 on weekend days only — twice a week. |
0 9 * * 1 | 0 9 * * 1 runs at 9:00 AM every Monday. | Fires once a week, Monday at 09:00. |
0 0 * * 0 | 0 0 * * 0 runs at midnight every Sunday. | Fires once a week, Sunday at 00:00. |
0 0 * * 5 | 0 0 * * 5 runs at midnight every Friday. | Fires once a week, Friday at 00:00. |
Monthly, quarterly and yearly
| Expression | What it means | When it runs |
|---|---|---|
0 0 1 * * | 0 0 1 * * runs at midnight on the 1st of every month. | Fires 12 times a year. |
0 0 1,15 * * | 0 0 1,15 * * runs at midnight on the 1st and the 15th of every month. | Fires 24 times a year. |
0 0 1 */3 * | 0 0 1 */3 * runs at midnight on the 1st of every third month. | Fires in January, April, July and October — 4 times a year. |
0 0 1 1,4,7,10 * | 0 0 1 1,4,7,10 * runs quarterly, at midnight on the 1st of January, April, July and October. | Fires 4 times a year. |
0 0 1 1 * | 0 0 1 1 * runs once a year, at midnight on 1 January. | Fires once a year. |
0 0 29 2 * | 0 0 29 2 * runs at midnight on 29 February. | Fires only in leap years — a date that does not exist is simply skipped. |
The @ shorthands
| Shorthand | Equivalent expression | What it means |
|---|---|---|
@hourly | 0 * * * * | @hourly runs once an hour, at the top of the hour. |
@daily / @midnight | 0 0 * * * | @daily runs once a day at midnight. |
@weekly | 0 0 * * 0 | @weekly runs once a week, at midnight on Sunday. |
@monthly | 0 0 1 * * | @monthly runs once a month, at midnight on the 1st. |
@yearly / @annually | 0 0 1 1 * | @yearly runs once a year, at midnight on 1 January. |
@reboot | — | @reboot runs once when the cron daemon starts, typically at boot. It is not time-based and has no equivalent expression. |
These shorthands are a Vixie cron feature — the implementation on nearly every Linux box — and they replace all five fields, which eliminates off-by-one slips. Cloud schedulers and language libraries vary in whether they accept them, so confirm before relying on one.
A note on two patterns you may see elsewhere: L (last day of month) and W (nearest weekday) are Quartz and AWS extensions, not standard cron. 0 0 L * * is a valid Quartz expression for "midnight on the last day of the month," but Vixie cron on Linux will reject it. Likewise 0 * * * * ? is a six-field Quartz expression — reading second minute hour day-of-month month day-of-week — that means "every minute, at second 0." Pasted into Linux crontab it is an error, because the ? is not standard and the field count is wrong.
Need an expression that is not listed here? The cron expression builder generates one from the schedule you pick, explains it field by field, and shows when it will next fire. And for a field-by-field breakdown of how step values behave, see what */5 * * * * really means.
Build and validate visually
Cron Expression Builder lets you construct an expression field by field, see it translated to plain English instantly, and preview the next several execution times so you can confirm it does what you intend before you deploy it.
A simple example, decoded
Let's break down the expression from the diagram one more time:
30 14 * * 1-5
Reading from left to right:
- 30: at 30 minutes past the hour
- 14: at hour 14 (2:00 PM in 24-hour format)
- *: every day of the month
- *: every month
- 1-5: Monday through Friday
This means "run at 2:30 PM every weekday" — the kind of schedule you'd use for a daily report generated each business afternoon.
Where cron came from
Cron traces back to the early Unix systems at Bell Labs; an early version is attributed to Ken Thompson, and the Version 7 Unix cron (around 1979) was written by Brian Kernighan. The name comes from chronos, the Greek word for time. That early daemon woke up once a minute, read a single system-wide table of jobs, and ran whatever was due.
The version almost everyone actually runs today is Vixie cron, written by Paul Vixie in 1987. Vixie's key addition was per-user crontabs — letting ordinary users schedule their own jobs without root — plus the @ macros above. By 1992 the crontab format was standardized in POSIX. If you want the practical difference between system-wide and per-user schedules, see our guide on the system crontab vs. user crontab.
Platform variations that break copy-paste
The five-field format is the baseline, but several major platforms diverge. Copying an expression between them is the single most common way cron schedules silently misfire.
| Platform | Fields | Format | Notable difference |
|---|---|---|---|
| Vixie cron (Linux) | 5 | min hour dom mon dow | Baseline; supports @ macros, not ? |
| Quartz (Java) | 6-7 | sec min hour dom mon dow [year] | Seconds field first; requires ? in one day field |
| AWS EventBridge | 6 | min hour dom mon dow year | Year is required; uses ?; no @ macros |
| Kubernetes CronJob | 5 | min hour dom mon dow | Standard 5-field; per-job time zone supported |
Spring @Scheduled | 6 | sec min hour dom mon dow | Seconds field first |
The takeaway: always check whether your scheduler expects five, six, or seven fields before pasting an expression. A five-field 0 0 * * * pasted into Quartz shifts every field one place to the left and schedules something entirely different.
Day of month vs. day of week: the OR rule
One subtlety catches even experienced developers. In standard Vixie cron, when both the day-of-month and day-of-week fields are restricted (neither is *), the job runs on days matching either condition — not both:
0 0 1 * 5 // Runs on the 1st of every month OR every Friday
If you expected "the 1st, but only if it's a Friday," this expression does the opposite of what you want. To get an AND-style condition you generally need application logic or a scheduler that supports it explicitly.
Common mistakes
- Using 7 for Sunday when your code expects 0 — both are valid in most implementations, but be consistent to avoid confusion.
- Forgetting the 0-based/1-based split — minutes and hours start at 0; days and months start at 1.
- Ignoring the server time zone — cron uses the server's local time, not the user's. Standardize on UTC for distributed systems.
- Assuming sub-minute precision — standard cron cannot schedule to the second; one minute is the floor.
- Pasting a five-field expression into a six-field scheduler — every field shifts and the schedule changes silently.
- Using
?on Linux — that's a Quartz/AWS feature; Vixie cron rejects it.
Best practices
Test before deploying. Use a validator or builder to confirm your expression and preview its next run times before it goes live. A wrong asterisk can turn "once a day" into "every minute."
Standardize on UTC. Running servers on UTC sidesteps the daylight-saving edge cases where a job runs twice or gets skipped.
Document each schedule. Cron is compact but not self-explanatory; a one-line comment above each entry saves future debugging.
Don't over-schedule. * * * * * runs 1,440 times a day — make sure that frequency is genuinely needed, and ensure runs can't overlap.
Plan for failure. Jobs can fail or run long. Add logging, alerting, and a guard against a slow run colliding with the next one.
Cron in application code
Beyond the operating system, nearly every language and platform speaks cron: node-cron and cron in Node.js, APScheduler in Python, Quartz and Spring's @Scheduled in Java, Kubernetes CronJobs, GitHub Actions workflows, and cloud schedulers like AWS EventBridge and Google Cloud Scheduler. The syntax knowledge transfers, but the field count and edge cases don't always — so for the patterns and pitfalls specific to running cron inside an app, see using cron expressions in applications.
Getting started
The fastest way to internalize cron is to build a few expressions and read them back. Start with the common patterns above, then reach for the special characters as your schedules get more specific. Use the Cron Expression Builder to create and validate expressions visually, with instant plain-English translations so you know exactly when your tasks will run.
Whether you're scheduling backups, automating reports, or orchestrating complex workflows, mastering these five fields unlocks powerful automation that saves hours and ensures critical tasks never get forgotten.