Cron Expression Syntax Explained (with 50 Examples) — 2026 Guide

#1Cron expression syntax: the mistakes that keep causing incidents
What we tested: We counted tokens across multiple model families (GPT-4o, Claude, Gemini) using our LLM token counter. Prompt caching behavior was tested with repeated inputs of varying length.
Most cron bugs are not syntax bugs. They are “the job ran at the wrong time” bugs: wrong timezone, wrong dialect, wrong field order, or a command that matched more often than intended.
The details that matter in practice are field order, scheduler differences, DST edge cases, and how to sanity-check a schedule before it runs in production.
#2The five fields
┌──── minute (0 - 59)
│ ┌── hour (0 - 23)
│ │ ┌─ day of month (1 - 31)
│ │ │ ┌ month (1 - 12 or JAN-DEC)
│ │ │ │ ┌ day of week (0 - 6 or SUN-SAT, 0 and 7 both = Sunday)
│ │ │ │ │
* * * * * commandEach field accepts these characters:
| Character | Meaning | Example |
|---|---|---|
* | Any value | * * * * *, every minute |
, | Value list | 0,15,30,45 * * * *, at 0, 15, 30, 45 minutes past every hour |
- | Range (inclusive) | 0 9-17 * * *, top of every hour from 9am to 5pm |
/ | Step | */15 * * * *, every 15 minutes; 0 */2 * * *, every 2 hours on the hour |
| Names | Aliases | 0 0 * * MON-FRI, midnight on weekdays |
A handful of dialects extend this. L = "last" (last day of month, last weekday), W = "nearest weekday," # = "Nth weekday of the month", these are Quartz and AWS only, not standard Linux cron. Use them and your expression will be rejected by /etc/crontab and crontab -e.
#2Reading and writing cron expressions safely
Cron syntax is unforgiving, one character in the wrong place runs your job hourly instead of daily, or never. Before you commit any cron expression to production, validate it two ways:
- In the browser. The Crontab Generator parses your expression, explains it in English, and lists the next 5–10 fire times. If those times do not match what you intended, your expression is wrong before it ever reaches the server.
- On the target system. Different cron implementations (Vixie cron on Debian, cronie on RHEL, Quartz on Java, Kubernetes CronJob controller) have subtle differences. Run
systemd-analyze calendar "<expression>"on systemd-based systems for an authoritative parse, or check the runtime's "next execution" log on first deploy.
You can also browse 50+ ready-to-use named patterns at the Cron Expressions Library, every entry shows the expression, an explanation, and copy-ready forms.
#2The Day-of-Month / Day-of-Week Trap
This is the single most-misread part of cron. When both day-of-month and day-of-week are specified (neither is *), Linux cron fires when either matches, it is an OR, not an AND.
0 0 1,15 * MONReads naturally as "first or fifteenth of the month, if it is a Monday." Wrong. This fires on the 1st, on the 15th, AND on every Monday. To express the intended meaning ("the 1st or 15th, but only when it is a Monday"), you need to filter at the script level:
0 0 1,15 * * [ "$(date +\%u)" = "1" ] && /path/to/jobQuartz / AWS uses ? in one of the two fields to mean "don't care" and avoid this ambiguity. Vixie cron does not, every Linux cron user should internalise the OR rule.
#2Standard Cron vs Quartz vs AWS vs Kubernetes vs GitHub Actions
The expression you type is the same length only by coincidence across these systems. Differences that bite in production:
| System | Fields | Seconds | Year | L W # | Notes |
|---|---|---|---|---|---|
| Linux Vixie cron | 5 | No | No | No | The OG. man 5 crontab |
| systemd timers | OnCalendar | Yes | Yes | No | Different syntax entirely (*-*-* 02:00:00) |
| Quartz (Java) | 6 or 7 | Yes | Optional year | Yes | Used by Spring @Scheduled |
| AWS EventBridge | 6 | No | Yes | Yes | Requires ? in one of DoM/DoW |
| Kubernetes CronJob | 5 | No | No | No | Same as Vixie, always UTC |
| GitHub Actions | 5 | No | No | No | Same as Vixie, always UTC; minimum interval 5 minutes |
| Cloudflare Cron Triggers | 5 | No | No | No | UTC; runs on Workers |
| Vercel Cron | 5 | No | No | No | UTC; one-minute minimum on paid plans |
The two takeaways:
- The same 5-field expression behaves differently on Linux versus Kubernetes versus GitHub Actions, Linux uses the local server timezone (which can be UTC or not), the others are always UTC. Same characters, different fire times.
- Quartz expressions copied from a Spring config will not work in Linux cron because of the extra seconds field. Strip the leading second, or your job runs once per minute of the hour you meant.
#250 Real-World Cron Examples
These are the patterns you actually use, grouped by intent. Each one is valid in standard Linux cron unless marked otherwise.
#3Every-N intervals
* * * * * # every minute (avoid in production, use systemd timer with OnUnitActiveSec)
*/5 * * * * # every 5 minutes
*/10 * * * * # every 10 minutes
*/15 * * * * # every 15 minutes (also: 0,15,30,45 * * * *)
*/30 * * * * # every 30 minutes
0 * * * * # every hour on the hour
17 * * * * # every hour at 17 minutes past (jittered hourly, see below)
0 */2 * * * # every 2 hours
0 */4 * * * # every 4 hours
0 */6 * * * # every 6 hours (00:00, 06:00, 12:00, 18:00)
0 */12 * * * # every 12 hours (00:00, 12:00)#3Daily
0 0 * * * # daily at midnight (avoid, see "jitter" section below)
0 3 * * * # daily at 3am
30 2 * * * # daily at 2:30am
17 4 * * * # daily at 4:17am (jittered, recommended)
0 9 * * MON-FRI # 9am on weekdays
0 17 * * MON-FRI # 5pm on weekdays
0 12 * * SAT,SUN # noon on weekends
0 9,17 * * MON-FRI # 9am and 5pm on weekdays
30 8 * * MON-FRI # 8:30am weekdays
0 8-18 * * MON-FRI # top of every hour from 8am to 6pm on weekdays#3Weekly / monthly / yearly
0 0 * * SUN # midnight every Sunday
0 3 * * SUN # 3am every Sunday
0 0 * * MON # midnight every Monday
0 9 * * MON # 9am every Monday (Monday standup reminder)
0 18 * * FRI # 6pm every Friday (weekly digest)
0 0 1 * * # midnight on the 1st of every month
0 0 15 * * # midnight on the 15th of every month
0 6 1,15 * * # 6am on the 1st and 15th (semi-monthly)
0 0 1 1 * # midnight on Jan 1 (yearly)
0 0 1 7 * # midnight on July 1 (Q3 kickoff)
0 0 1 */3 * # midnight on the 1st of every 3rd month (Jan, Apr, Jul, Oct, quarterly)#3Business hours
0 9-17 * * MON-FRI # top of every hour, business hours, weekdays
*/15 9-17 * * MON-FRI # every 15 minutes, business hours, weekdays
0,30 9-17 * * MON-FRI # every 30 minutes, business hours, weekdays
0 8-20/2 * * MON-FRI # every 2 hours from 8am to 8pm, weekdays
0 9-17 1-5 * MON-FRI # first work-week of the month, business hours, weekdays#3Off-hours / overnight batches
0 1 * * * # 1am daily (DB maintenance)
0 2 * * * # 2am daily (log rotation)
0 3 * * * # 3am daily (backup)
30 2 * * SUN # 2:30am every Sunday (weekly backup)
0 1 * * MON-FRI # 1am every weekday (overnight ETL)
0 0 1 * * # midnight on the 1st (monthly close)
0 23 * * SUN # 11pm Sunday (start of week prep)#3Cache warmers / health checks (avoid bursts)
The pattern: pick a non-zero, non-:30 minute to keep your fleet from hammering a downstream all at once.
7 * * * * # hourly at :07
17 * * * * # hourly at :17
23 * * * * # hourly at :23
37 * * * * # hourly at :37
*/7 * * * * # every 7 minutes (irrational vs typical 5/10, natural jitter)#3Quartz / AWS-only (L, W, #)
0 0 L * ? # midnight on the last day of every month (Quartz / AWS)
0 0 LW * ? # midnight on the last weekday of every month
0 0 ? * 1#1 # midnight on the first Sunday of every month (Quartz)
0 0 ? * 6#3 # midnight on the third Friday of every month
0 0 ? * 2L # midnight on the last Monday of every monthThese will not work in Linux cron. Use only with Quartz, AWS EventBridge, or compatible Spring @Scheduled configs.
#3Special strings (Vixie cron extensions)
@reboot # at startup
@yearly / @annually # 0 0 1 1 *
@monthly # 0 0 1 * *
@weekly # 0 0 * * 0
@daily / @midnight # 0 0 * * *
@hourly # 0 * * * *These are not part of POSIX cron but are supported by Vixie, cronie, and most modern implementations. Not supported in Kubernetes CronJob, always use the explicit 5-field form there.
#2The Jitter Problem
Look at any cloud provider's traffic dashboard at exactly :00 past the hour and you will see a spike. Every "hourly" cron everyone wrote, 0 * * * *, fires at the same instant globally. Same with 0 0 * * * at midnight UTC.
If your job hits a third-party API or shared infrastructure, picking :00 is the worst possible minute. Pick something off-pattern:
- Hourly:
17 * * * *or23 * * * *or*/7 * * * *, anything that is not 0 or 30. - Daily:
17 3 * * *rather than0 3 * * *. - Weekly:
23 2 * * SUNrather than0 0 * * SUN.
If you run thousands of identical workloads, fleet of devices reporting telemetry, scheduled batch across N tenants, add per-instance jitter in the job itself: sleep $((RANDOM % 60)) at the start, or derive the minute from a hash of the instance ID. Otherwise you ship a fleet-wide thundering herd every hour.
#2Timezone and DST Traps
Three rules that will save you a 3am page:
- Cron uses the system timezone (Linux) or always UTC (Kubernetes, GitHub Actions, Vercel, Cloudflare, AWS). Run
timedatectlon Linux to confirm. A schedule of0 9 * * MON-FRImeans 9am for whoever the server thinks is local, if your server moves to UTC, your "9am" suddenly fires at 5am Eastern. - DST changes silently skip or duplicate runs. On "spring forward" (typically 2am), a
30 2 * * *job never fires that day. On "fall back," it fires twice. Modern cron implementations (cronie, Vixie since 2014) handle this by skipping the spring jump and not duplicating the fall back, but Quartz and many cloud crons do not. If a job runs near 2am local time and the consequences of a miss or duplicate matter, schedule it during a DST-safe window (4am or later) or use UTC explicitly. - Kubernetes CronJob timezone hint exists since 1.27.
spec.timeZone: "America/New_York"lets you write a local schedule without converting to UTC by hand. The schedule is still evaluated in UTC under the hood, the timeZone field is the conversion. Earlier Kubernetes versions: schedule in UTC and document the offset, period.
#2Three Cron Antipatterns
#31. Daily backups at 0 0 * * *
Across thousands of servers, "midnight UTC" is the worst time you can pick, every other team picked it too, your VM provider's snapshot service is saturated, your downstream backup target is rate-limited. Move to 17 3 * * * or similar, and if you control a fleet, spread the fleet across the hour with per-instance hashing.
#32. * * * * * for "every minute" production tasks
A job that runs every minute is almost always a job that should be a long-running process with a sleep loop, or a systemd timer with OnUnitActiveSec=1min. Cron at 1-minute granularity has the worst characteristics of both: high overhead (fork + bootstrap per minute) and no built-in serialisation (overlapping runs if one minute's work takes longer than 60 seconds).
If you must use 1-minute cron, add a lock at the application level: flock -n /tmp/myjob.lock /path/to/job.sh ensures only one runs at a time.
#33. Catching up after downtime, silently
By default, if cron is down (server reboot, container restart), missed runs are gone. Some implementations (anacron on Linux) catch up; most do not. Kubernetes CronJob has spec.startingDeadlineSeconds and concurrencyPolicy: Forbid|Allow|Replace for this, but the defaults will silently drop missed runs.
If your job is "send the daily digest at 9am," that may be fine. If it is "process every minute's batch of payment events," missing 90 minutes of runs is a disaster. Either alert on missed runs (Prometheus time() - last_success_timestamp > N) or use a runtime with explicit at-least-once semantics for missed schedules (Temporal, Airflow with catchup=True, AWS EventBridge Pipes).
#2Frequently Asked Questions
#3How do I read a cron expression?
Left to right, five fields separated by spaces: minute, hour, day-of-month, month, day-of-week. Each field is a number, a range like 1-5, a comma list like 1,3,5, a step like */15, or * for any value. So 0 9 * * MON-FRI is "minute 0, hour 9, every day of the month, every month, Monday through Friday", i.e., 9am on weekdays. The fastest way to confirm an expression in 2026 is the Crontab Generator, which shows the next fire times as you type. Misreading a single character changes the schedule entirely, so always validate before deploying.
#3What is the difference between standard cron and Quartz cron?
Three things. (1) Quartz expressions have six or seven fields, a leading seconds field that standard cron does not, and an optional trailing year. Quartz 0 0 9 * * MON-FRI is "second 0, minute 0, hour 9, weekdays", copy that into Linux cron and it runs once per minute instead of once per day, because Linux interprets the leading 0 as "minute 0." (2) Quartz supports L, W, # for last-of-month, nearest-weekday, and Nth-weekday, Linux cron does not. (3) Quartz requires ? in one of the day-of-month or day-of-week fields when the other is specified, which is how it avoids the Linux OR-ambiguity. Quartz is the dialect used by Spring @Scheduled, Apache Camel, and AWS EventBridge (with minor variants).
#3What does 0 0 * * 0 actually do?
Midnight every Sunday. Field by field: minute 0, hour 0, any day of month, any month, day-of-week 0 (Sunday). The mild gotcha is that day-of-week numbering varies across systems, most use 0 = Sunday and 6 = Saturday (with 7 also valid for Sunday on Linux), but Quartz uses 1 = Sunday and 7 = Saturday. The named form SUN is unambiguous and works in both. When in doubt, type the expression into the Crontab Generator and verify the next fire time matches your intent.
#3Why do my cron jobs not run when expected?
The five usual suspects: (1) timezone mismatch, your job's "9am" is the server's 9am, which may be UTC, not your local time. (2) the day-of-month / day-of-week OR rule, 0 0 1,15 * MON fires on the 1st, the 15th, AND every Monday, not "the 1st or 15th if it is a Monday." (3) environment, cron runs with a minimal PATH and no shell profile; absolute paths and explicit PATH=... lines are non-optional. (4) DST transitions silently skip or duplicate runs near 2am local; schedule outside that window. (5) silent missed runs, if cron was down at the scheduled time, the run is gone. Alert on missed runs explicitly. Validate the expression itself with the Crontab Generator before suspecting a deeper bug.
#3What is the smallest interval a cron expression can express?
One minute, standard cron has minute granularity, no sub-minute support. If you need every 30 seconds, either use a Quartz seconds field (*/30 * * * * ? in Quartz dialect), a systemd timer with OnUnitActiveSec=30s, or a long-running process with an internal sleep 30 loop. Cloud cron services typically have their own minimums: GitHub Actions and Vercel both require at least 5 minutes between runs on free plans. Below one-minute granularity, cron is the wrong abstraction, reach for a daemon, a queue, or a stream processor instead.
Cron expressions look intimidating until you have written ten of them; they look obvious after a hundred. The fastest way to get to the hundred is the Crontab Generator for live parsing, the Cron Expressions Library for copy-paste named patterns, and the muscle memory that comes from doing it weekly.
Generate, validate, and explain any cron expression now at the AllDevToolsHub Crontab Generator, runs entirely in your browser, shows the next five fire times for any input, supports standard, Quartz, and AWS dialects. For ready-to-use named patterns (hourly, daily, weekly, monthly, on-the-quarter, off-hours), browse the Cron Expressions Library. For the broader scheduling and devops surface, see the Terraform HCL validation guide and the Regex Cheatsheet.
Written by Rahul Jalavadiya, founder of AllDevToolsHub. All tools run locally in your browser.
#2Sources / Further reading
- POSIX - crontab manual page
- man7 - cron(8) Linux manual
- IETF - RFC 5545: iCalendar (RRULE syntax)
#2Try These Tools
Quick Summary
>- A complete, practical guide to cron expressions in 2026. Five-field standard cron and the six- and seven-field variants (Quartz, AWS, Kubernetes), every special character (* , - / L W # ?), 50 real-world examples grouped by use case, timezone and DST traps, the difference between Linux cron, Kubernetes CronJob, and GitHub Actions schedule, and how to test expressions safely.
Key Takeaways
- Standard cron uses 5 fields: minute, hour, day-of-month, month, day-of-week. Each field supports ranges, lists, and steps.
- Day-of-month and day-of-week interact non-obviously: if both are restricted (not *), the job fires when EITHER matches.
- Special strings like @daily, @hourly, and @reboot are supported by most cron implementations but are not POSIX standard.
When to use it
- Scheduling nightly database backups at 2:00 AM.
- Running a weekly report generation every Monday at 9 AM.
- Setting up health checks every 5 minutes during business hours.
Common Mistakes
- Assuming day-of-month and day-of-week are AND-ed — in Vixie cron, they are OR-ed when both are restricted.
- Using */5 in the minute field when you actually need specific minutes (0,10,20...) — */5 starts from 0.
- Forgetting that cron runs in the server's timezone — always verify with TZ environment variable.
Cron Expression Syntax Explained (with 50 Examples) — 2026 Guide, Frequently Asked
What happens when both day-of-month and day-of-week are specified?
In standard Vixie cron (Linux), the job runs when EITHER field matches (OR logic). If you need AND logic, you must handle it in the command itself.
How do I run a cron job every 5 minutes?
Use */5 * * * * which fires at minutes 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55 of every hour.
Tools Mentioned in This Article
Crontab Expression Generator
Build and validate cron expressions with a visual editor.
Cron Job Expression Generator
Visual builder for crontab schedule patterns.
.env File Parser
Parse, edit, and export .env files as JSON, Docker flags, or shell exports.
.gitignore Generator
Generate .gitignore files for any language, framework, OS, or editor.
Tools, tactics, and toughened-up tips, once a week
New tools, deep-dives on developer workflows, and the occasional gem we found this week. No spam, no tracking. Unsubscribe anytime.
Found an error or have feedback?
We correct errors quickly and document changes in our changelog. Report issues at support@alldevtoolshub.com.