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.
That paragraph is the summary an AI overview gives you. The rest of this guide is the part it can't: the exact range of every field, what each special character actually does (including the ones that trip people up), the platform variations that silently break a copy-pasted expression, and a reference table of patterns you can lift directly. This is the pillar of our cron series — when you want to go deeper on one piece, we link the focused guides inline.
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.
Macros: the @ shortcuts
Vixie cron (the implementation on nearly every Linux box) accepts named shortcuts that replace all five fields. They read better and eliminate off-by-one slips.
| Macro | Equivalent | Runs |
|---|---|---|
@yearly / @annually | 0 0 1 1 * | Once a year, midnight Jan 1 |
@monthly | 0 0 1 * * | Once a month, midnight on the 1st |
@weekly | 0 0 * * 0 | Once a week, midnight Sunday |
@daily / @midnight | 0 0 * * * | Once a day, midnight |
@hourly | 0 * * * * | Once an hour, on the hour |
@reboot | — | Once, when the cron daemon starts |
@reboot is the odd one out: it is not time-based at all. It fires a single time when cron starts (typically at boot), which makes it handy for starting a long-running helper process. Not every scheduler supports these macros — cloud schedulers and language libraries vary — so confirm before relying on them.
Common patterns you can copy
Here is a reference of expressions that cover the schedules people actually need. Lift them directly.
| Expression | Schedule |
|---|---|
* * * * * | Every minute |
*/5 * * * * | Every 5 minutes |
*/15 * * * * | Every 15 minutes |
0 * * * * | Every hour, on the hour |
0 */2 * * * | Every 2 hours |
0 0 * * * | Daily at midnight |
0 2 * * * | Daily at 2 AM (common backup window) |
30 14 * * 1-5 | 2:30 PM on weekdays |
0 9-17 * * 1-5 | Every hour, 9 AM-5 PM, weekdays |
*/15 9-17 * * 1-5 | Every 15 min during business hours, weekdays |
0 9 * * 1 | 9 AM every Monday |
0 0 * * 0 | Midnight every Sunday |
0 0 1 * * | Midnight on the 1st of each month |
0 0 1,15 * * | Midnight on the 1st and 15th |
0 0 1 1,4,7,10 * | Quarterly (1st of Jan, Apr, Jul, Oct) |
0 0 1 1 * | Once a year, midnight Jan 1 |
Want to build one for a schedule that isn't here? Use the interactive builder below — pick the values and it generates the expression with a plain-English translation and the next run times.
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 the next run times. 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.