Overview
Cron expressions are the short, five-field strings that tell a scheduler exactly when to run a recurring job — from a nightly backup to a weekly report email. The syntax is compact enough to be confusing at first glance, but it follows a small, consistent set of rules once you understand the order of fields and the handful of special characters used to build patterns.
This guide walks through reading and writing the standard 5-field cron syntax, the wildcards and operators that build common patterns, and the tools to validate or generate an expression before it goes anywhere near production. Use the Cron Expression Validator to confirm any expression's behavior, and the Cron Expression Generator for schedules that are harder to write correctly by hand.
What You Need
Before writing a cron expression, know:
- The exact schedule you want — stated in plain language first ("every weekday at 6 PM," "the 1st of every month at midnight")
- Your system's cron implementation — standard 5-field Unix cron, or an extended format like Quartz that adds seconds and sometimes year
- The timezone your scheduler runs in — server local time or an explicitly configured timezone
- Whether day-of-week starts at 0=Sunday or 1=Monday in your specific system, since this convention is not universal
Step 1: Understand the 5-Field Syntax
A standard cron expression has exactly five fields, separated by spaces, always in this order:
minute hour day-of-month month day-of-week
0-59 0-23 1-31 1-12 0-6 (0 = Sunday)
Each field can be a specific number, a wildcard, or a more complex pattern built from the special characters covered in Step 2. For example, the expression 30 14 * * * means minute 30, hour 14, every day of the month, every month, every day of the week — which translates to "every day at 2:30 PM."
Reading a cron expression left to right, in field order, is the most reliable way to avoid misinterpreting it — never assume the order based on what "sounds natural," since some other scheduling systems do order fields differently.
Step 2: Use Wildcards and Special Characters
Four characters build almost every cron pattern you will ever need:
- Asterisk (
*) — means "every" value for that field.* * * * *runs every single minute. - Comma (
,) — creates a list of specific values.1,15in the day-of-month field means the 1st and the 15th of the month. - Hyphen (
-) — creates an inclusive range.1-5in the day-of-week field means Monday through Friday (assuming the common 0=Sunday, 1=Monday convention). - Slash (
/) — creates a step value, meaning "every Nth value starting from the field's minimum."*/15in the minute field means every 15 minutes: 0, 15, 30, 45.
These four characters can be combined within a single field. For example, 0,30 combined with a range like 9-17 in the hour field — written as part of a full expression 0,30 9-17 * * * — runs at the top and bottom of every hour between 9 AM and 5 PM.
Step 3: Write Common Scheduling Patterns
A small set of patterns covers the vast majority of real-world scheduling needs:
| Schedule | Expression |
|---|---|
| Every day at midnight | 0 0 * * * |
| Every 15 minutes | */15 * * * * |
| Every Monday at 9 AM | 0 9 * * 1 |
| First day of every month at midnight | 0 0 1 * * |
| Every weekday at 6 PM | 0 18 * * 1-5 |
Notice the pattern: the leftmost fields (minute, hour) are usually specific numbers when you want a precise time, while the rightmost fields (day-of-month, month, day-of-week) stay as wildcards unless you specifically need to restrict the day or month. Building expressions by starting with the time-of-day fields and working outward to the day-restriction fields is a reliable way to construct them correctly from scratch.
Step 4: Validate Your Expression Before Deploying
Even simple-looking cron expressions are easy to get subtly wrong — a misplaced field, an inverted range, or a day-of-week numbering mismatch can all silently schedule a job for the wrong time. Before deploying any expression to production, run it through the Cron Expression Validator, which checks syntax correctness and shows the next several scheduled run times in human-readable form.
Seeing the actual upcoming run times — not just a syntax-valid checkmark — is the only reliable way to confirm the expression does what you intended. A syntactically valid expression can still run at completely the wrong time if, for example, you intended "every weekday" but wrote a range that actually includes the weekend due to a numbering mismatch.
Step 5: Generate Complex Schedules
Some schedules are difficult or impossible to express cleanly using only the five basic fields and four operators — "the last day of the month," "every other Tuesday," or "the second Friday of each quarter" all require either non-standard extensions or external logic that pure cron syntax cannot express on its own.
For these less common patterns, use the Cron Expression Generator to describe the schedule in plain terms and receive a correctly constructed expression — or, where standard cron genuinely cannot express the pattern, guidance on the application-level logic needed to supplement it (such as checking the date inside the job itself before deciding whether to actually execute the task).
Common Mistakes to Avoid
Confusing day-of-month and day-of-week field order across different systems. While the standard 5-field order is minute, hour, day-of-month, month, day-of-week, not every scheduling system follows this exactly — Quartz, for example, uses a 6 or 7-field format that adds a seconds field at the start and an optional year field at the end, shifting everything else over by one position. Always check your specific cron implementation's documentation rather than assuming the generic 5-field order applies universally.
Off-by-one errors with day-of-week numbering. Some systems number day-of-week starting at 0=Sunday, while others start at 0=Monday or use 1=Sunday — and mixing up these conventions silently shifts every day-restricted schedule by one day. Before deploying any expression using the day-of-week field, verify your specific system's convention against its documentation, and confirm using the validator's human-readable preview rather than assuming the most common convention applies.
Timezone confusion between server local time and UTC. Most cron daemons execute based on the server's local system time, not UTC, unless a scheduler is explicitly configured to use a fixed timezone. This causes two common failure modes: a job silently shifting by one hour after a daylight saving time transition in regions that observe DST, and a job running at an unexpected hour after being migrated to a server in a different timezone. Document the intended timezone alongside every cron expression, and prefer UTC-based scheduling configurations where your platform supports it, to avoid DST-related drift entirely.
Formula & Methodology
Underneath the syntax, a cron daemon's matching logic is simple: once every minute, it reads the current system time and checks it against every registered expression's five fields. If the current minute matches the minute field, the current hour matches the hour field, and so on for all five fields — treating any wildcard as an automatic match — the daemon triggers that job's execution. This per-minute polling is why standard cron has an inherent one-minute scheduling resolution; sub-minute scheduling requires a different tool or a 6-field format with an added seconds field, such as Quartz.
The one subtlety that catches most users by surprise is how day-of-month and day-of-week interact when both fields are restricted away from their wildcard default. Intuitively, many people expect an AND relationship — "run only when both the date and the weekday match." In practice, most standard cron implementations apply an OR relationship instead: the job runs if either field's condition is satisfied. The expression 0 0 1,15 * 1 therefore runs at midnight on the 1st of the month, on the 15th of the month, AND on every Monday — not only on Mondays that happen to land on the 1st or 15th. This OR behavior is specified in the original cron design and persists across most modern implementations, but it is rarely intuitive on first encounter, which is exactly why previewing actual upcoming run times through a validator — rather than reasoning about the fields in isolation — is the safest way to confirm an expression behaves as intended before it reaches production.