Cron expressions look cryptic at first glance — five space-separated fields packed with asterisks, numbers, slashes, and commas. But there is a consistent logic to every field, and once you understand it, reading and writing cron schedules becomes straightforward. This guide breaks down every part of cron syntax, explains the special characters, shows the most common schedule patterns, and covers the tools that resolve, validate, and simulate cron expressions before you deploy them.
What is a cron expression?
A cron expression is a string of five (or sometimes six) space-separated fields that defines a recurring schedule for automated tasks. The format was invented with the Unix cron daemon in the early 1970s and has remained essentially unchanged ever since. Today it is used in Linux crontab files, CI/CD pipelines like GitHub Actions and GitLab CI, cloud schedulers like AWS EventBridge and Google Cloud Scheduler, container orchestrators like Kubernetes CronJob, and application frameworks including Spring and Quartz.
The anatomy of a cron expression
The standard five-field format is:
┌─────────── minute (0–59)
│ ┌───────── hour (0–23)
│ │ ┌─────── day of month (1–31)
│ │ │ ┌───── month (1–12)
│ │ │ │ ┌─── day of week (0–7, Sunday = 0 or 7)
│ │ │ │ │
* * * * * command to executeFields are read left to right: minute, hour, day-of-month, month, day-of-week. The expression "0 9 * * 1-5" fires at 09:00 on every weekday. Each field can hold a single value, a comma-separated list, a range, a step, a wildcard, or a combination. Understanding these operators is the entire skill of reading cron.
Note
Cron expression syntax field by field
Each field has a defined range of valid integer values. Values outside the valid range produce an error or silent misconfiguration depending on the cron implementation. Understanding the valid range for each field is the foundation of reading any cron expression.
Field valid ranges
- Minute — 0 to 59. Value 0 is the top of the hour; value 59 is one minute before the next hour.
- Hour — 0 to 23. Uses 24-hour format: 0 is midnight, 12 is noon, 23 is 11 PM.
- Day of month — 1 to 31. Days are 1-indexed. Some months have fewer days — February has 28 or 29, April/June/September/November have 30.
- Month — 1 to 12. January is 1, December is 12. Some implementations also accept three-letter abbreviations: JAN, FEB, MAR, etc.
- Day of week — 0 to 7. Both 0 and 7 represent Sunday. Monday is 1, Saturday is 6. Some implementations support MON, TUE, WED, THU, FRI, SAT, SUN.
How field interactions work
The five fields are evaluated together as a logical AND — the job runs when all non-wildcard fields match simultaneously. "0 9 15 * *" fires at exactly 09:00 on the 15th of every month. The critical exception is when both day-of-month and day-of-week are non-wildcard values — in that case, most cron daemons fire if EITHER condition is true (a logical OR), not both. This is a notorious source of unexpected behavior.
Warning
Special characters explained
Cron's power comes from its special characters — the operators that turn fixed values into flexible schedule patterns. Mastering these six operators covers virtually every scheduling pattern you will encounter.
The six core operators
| Character | Name | Example | Meaning |
|---|---|---|---|
| * | Wildcard | * in minute | Every valid value (0–59 for minutes) |
| , | List | 1,15,30 in minute | At minutes 1, 15, and 30 |
| - | Range | 1-5 in dow | Monday through Friday (days 1 to 5) |
| / | Step | */15 in minute | Every 15 minutes (0, 15, 30, 45) |
| ? | No-spec | ? in dom | No specific value (Quartz/Spring only) |
| @ | Macro | @daily | Shorthand aliases (@daily = 0 0 * * *) |
Combining operators
Operators can be combined within a single field. "0,30 9-17 * * 1-5" means "at minutes 0 and 30 of every hour from 9 AM to 5 PM, Monday through Friday." Step syntax can also be applied to a range: "0-30/5" in the minute field means every 5 minutes from minute 0 to minute 30 (0, 5, 10, 15, 20, 25, 30). The Cron to Human-Readable Translator breaks down any combination into plain English with field-by-field explanations.
Tip
Common cron schedule patterns
The majority of real-world cron schedules fall into a handful of recurring patterns. Knowing these patterns by sight means you can read most production crontabs without a reference.
- "* * * * *" — Every minute. The most frequent possible schedule. Used for heartbeat checks and monitoring.
- "*/5 * * * *" — Every 5 minutes. Common for polling jobs and cache warmers.
- "0 * * * *" — Every hour on the hour. Standard for hourly batch processes.
- "0 0 * * *" — Daily at midnight (UTC). The default for daily cleanup jobs.
- "0 9 * * 1-5" — 9 AM every weekday. Standard business-hours schedule.
- "0 0 1 * *" — Midnight on the 1st of every month. Monthly billing runs, report generation.
- "0 0 1 1 *" — Midnight on January 1st. Annual jobs — license renewals, year-end reports.
- "0 0 * * 0" — Midnight every Sunday. Weekly maintenance windows.
Building custom schedules from patterns
Most complex schedules are combinations of these patterns. "0 2 * * 6,0" means "2 AM on Saturday and Sunday" — the weekend maintenance window. "*/10 8-18 * * 1-5" means "every 10 minutes from 8 AM to 6 PM, weekdays only." If you know the target schedule in plain English, the Crontab Expression Builder lets you configure each field visually and generates the correct expression with a human-readable confirmation and the next five run times.
Crontab Expression Builder
Build cron expressions visually with 15 presets, 5 field modes, instant human-readable description, and next 5 run time preview — free, no signup.
How to build and validate cron expressions
Writing a cron expression from scratch and hoping it is correct is risky. A single transposed field or a misunderstood operator can cause a job to fire at completely wrong times — or never fire at all. These tools eliminate that risk.
Write or select your expression
Open the Crontab Expression Builder and either type your expression directly or choose from the 15 built-in presets. Each preset covers a common schedule pattern (every minute, hourly, daily, weekdays, monthly) and can be adjusted using the visual field controls without touching the raw expression.
Validate for syntax errors
Paste the expression into the Cron Expression Validator. It checks each field against its valid range, validates range boundaries (start must be less than end), confirms step values are non-zero, and flags macros that are not supported by standard 5-field crontab. Each error comes with a line-level message explaining what the parser expected.
Translate to plain English
Run the validated expression through the Cron to Human-Readable Translator. The output is a full plain-English sentence describing the schedule — "At 2:30 AM, on Monday, Wednesday, and Friday" — plus a field-by-field breakdown. If the description does not match your intent, revise the expression before deploying.
Simulate the next 12 months of runs
For scheduled jobs where timing matters — billing runs, data exports, maintenance windows — open the Interactive Cron Scheduler and simulate the expression over the next 12 months. The calendar view shows monthly run density, and the timeline shows exact timestamps. Confirm the schedule fires on the expected dates before merging the configuration to production.
Cron Expression Validator
Validate 5-field cron syntax, ranges, steps, list values, and macros with instant field-level diagnostics — browser-local, free, no signup.
Cron in CI/CD and cloud schedulers
Cron syntax is used far beyond the Linux crontab. Modern CI/CD and cloud platforms have all adopted the same 5-field format, but each has its own quirks around timezone handling, macro support, and minimum interval restrictions.
Platform-by-platform differences
| Platform | Fields | Timezone | Macros | Min interval |
|---|---|---|---|---|
| Linux crontab | 5 | System local | ✓ All 6 | Every minute |
| GitHub Actions | 5 | UTC only | ✗ None | Every 5 min (enforced 15 min drift) |
| GitLab CI | 5 | UTC only | ✓ Partial | Every minute |
| AWS EventBridge | 5 or 6 | UTC only | ✗ None | Every minute |
| Google Cloud Scheduler | 5 or unix | Any IANA tz | ✓ Some | Every minute |
| Kubernetes CronJob | 5 | Cluster tz | ✗ None | Every minute |
| Spring Scheduler | 6 | JVM default | ✓ Yes | Every second |
GitHub Actions cron scheduling
GitHub Actions uses standard 5-field cron syntax but always runs in UTC. If you want a job at 9 AM Eastern time (UTC-5), you write "0 14 * * *". GitHub Actions also does not support macro syntax (@daily, @weekly), so use the full expression. Scheduled workflows during periods of high load may fire up to 15 minutes late — design your jobs to be tolerant of this drift.
Converting cron to systemd timers
Modern Linux systems increasingly use systemd timers instead of classic crontab. Systemd's OnCalendar format uses different syntax but covers all the same patterns. The Cron to Systemd Timer Converter converts any 5-field cron expression to its equivalent systemd timer unit configuration, including a complete .timer and .service file template.
Cron pitfalls and edge cases
Even experienced developers encounter silent failures with cron. These are the most common pitfalls — each one has cost production systems unnoticed downtime or duplicate job runs.
Timezone surprises
Cron runs in the server's local timezone by default. If your server is in UTC but your business is in New York (UTC-5), "0 9 * * *" fires at 4 AM local time, not 9 AM. Cloud platforms like GitHub Actions and AWS EventBridge always run in UTC, which means all timezone math falls on you. Always confirm the timezone before deploying a schedule. The Interactive Cron Scheduler lets you simulate runs with timezone offset applied.
Month-end edge cases
Scheduling a job on the 29th, 30th, or 31st of the month causes it to silently skip months that do not have that date. "0 0 31 * *" never runs in April, June, September, or November. "0 0 29 2 *" runs in February only on leap years. If you need a "last day of the month" schedule, you need a smarter approach — either run daily with a script that checks the date, or use a cloud scheduler that supports L (last) day syntax.
DST transitions
Daylight saving time transitions can cause cron jobs to run twice or be skipped. When clocks spring forward, times in the skipped hour never occur. When clocks fall back, times in the repeated hour occur twice. Jobs scheduled in a local timezone during the transition window are affected. The safest practice is to run servers and schedulers in UTC, which never has DST transitions.
Warning
The every-second problem
Standard 5-field cron has a minimum resolution of one minute. If you need a job to run every 10 seconds, cron is not the right tool — use a system timer, a job queue with delays, or the seconds field available in Spring Scheduler and Quartz. Trying to approximate sub-minute schedules with multiple crontab entries is error-prone and creates race conditions when jobs overlap.
Key takeaways
- A cron expression has five fields: minute (0–59), hour (0–23), day-of-month (1–31), month (1–12), day-of-week (0–7).
- The six special characters are * (wildcard), , (list), - (range), / (step), ? (no-spec, Quartz only), and @ (macros like @daily).
- When both day-of-month and day-of-week are non-wildcard, most cron daemons fire if EITHER matches — a common source of unexpected double-runs.
- Use the Crontab Expression Builder to build visually, the Cron Expression Validator to check syntax, and the Interactive Cron Scheduler to simulate 12 months of runs.
- GitHub Actions uses 5-field UTC-only cron with no macro support; AWS EventBridge and Google Cloud Scheduler also use UTC; Linux crontab uses the system local timezone.
- Do not schedule on the 29th, 30th, or 31st if you need the job to run every month — those days do not exist in every month.
- Standard cron has a one-minute minimum resolution; use systemd timers, job queues, or Spring/Quartz for sub-minute scheduling needs.