Cron Expressions Demystified: Schedule Anything
Master cron expression syntax for scheduling tasks — every field, special characters, common schedules, and gotchas that trip up experienced developers.
Cron expressions are the universal language for scheduling recurring tasks. They appear in Unix crontabs, CI/CD pipelines, cloud schedulers, Kubernetes CronJobs, and dozens of task scheduling libraries. The syntax is compact, powerful, and — if you have not memorized it — occasionally confusing.
This guide makes cron expressions click, covering every field, every special character, and the gotchas that trip people up.
The Five Fields
A standard cron expression has five fields separated by spaces:
┌───────────── minute (0-59)
│ ┌───────────── hour (0-23)
│ │ ┌───────────── day of month (1-31)
│ │ │ ┌───────────── month (1-12)
│ │ │ │ ┌───────────── day of week (0-7, where 0 and 7 are Sunday)
│ │ │ │ │
* * * * *
Each field specifies when the job should run for that time unit. The job executes when all five fields match the current time.
Some systems (like Quartz and Spring) use six or seven fields, adding seconds and/or year. This guide covers the standard five-field format used by cron and most scheduling systems.
Special Characters
Asterisk * — Every Value
Matches every value for that field:
* * * * * # every minute of every hour of every day
Comma , — Multiple Values
Lists specific values:
0 9,17 * * * # at 9:00 AM and 5:00 PM daily
Hyphen - — Range
Specifies a continuous range:
0 9-17 * * * # every hour from 9 AM to 5 PM
Slash / — Step
Specifies intervals:
*/15 * * * * # every 15 minutes (0, 15, 30, 45)
0 */2 * * * # every 2 hours (0, 2, 4, 6, ...)
The step applies from the start of the range. */15 in the minute field starts from 0, so it fires at 0, 15, 30, and 45.
You can combine ranges with steps:
5-55/10 * * * * # every 10 minutes starting at 5 (5, 15, 25, 35, 45, 55)
Common Schedules
Here are the cron expressions you will use most often:
# Every minute
* * * * *
# Every 5 minutes
*/5 * * * *
# Every hour (at minute 0)
0 * * * *
# Every day at midnight
0 0 * * *
# Every day at 9:30 AM
30 9 * * *
# Every Monday at 9:00 AM
0 9 * * 1
# Weekdays at 8:00 AM
0 8 * * 1-5
# First day of every month at midnight
0 0 1 * *
# Every quarter (Jan, Apr, Jul, Oct) on the 1st at midnight
0 0 1 1,4,7,10 *
# Every Sunday at 3:00 AM (good for maintenance)
0 3 * * 0
# Twice daily at 6 AM and 6 PM
0 6,18 * * *
# Every 30 minutes during business hours on weekdays
*/30 9-17 * * 1-5
Month and Day of Week Names
Most cron implementations accept three-letter abbreviations:
# Months: JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC
0 0 1 JAN,JUL * # midnight on Jan 1 and Jul 1
# Days: SUN, MON, TUE, WED, THU, FRI, SAT
0 9 * * MON-FRI # 9 AM on weekdays
Names are case-insensitive in most implementations and make expressions more readable.
Gotchas and Edge Cases
Day of Month AND Day of Week
When both day of month and day of week are specified (neither is *), the behavior varies by implementation:
Standard cron (Linux): The job runs if EITHER condition matches (OR logic):
0 0 15 * FRI # runs on the 15th AND every Friday (not "the 15th if it's a Friday")
Quartz scheduler: Uses AND logic — both must match.
This difference is a major source of confusion. If you want “the 15th, but only if it is a Friday,” you need to handle that logic in your script, not in the cron expression (in standard cron).
Time Zones
Cron jobs run in the system’s configured timezone by default. In crontabs, you can set:
CRON_TZ=America/New_York
0 9 * * * # 9 AM Eastern Time
Kubernetes CronJobs and cloud schedulers typically default to UTC. Always specify the timezone explicitly to avoid surprises, especially around DST transitions.
DST Transitions
When clocks spring forward (2 AM becomes 3 AM), jobs scheduled between 2:00 and 2:59 AM may be skipped. When clocks fall back, jobs in that window may run twice. Schedule critical jobs outside the DST transition window or use UTC.
No “Last Day of Month”
Standard cron has no way to express “last day of the month.” Since months have 28-31 days, 0 0 28-31 * * would run on the 28th, 29th, 30th, and 31st (whichever exist). For “last day only,” you need to check in your script:
# Run at midnight, but only on the last day of the month
0 0 28-31 * * [ "$(date -d tomorrow +\%d)" = "01" ] && /path/to/script.sh
Some extended cron implementations (Quartz) support L for “last.”
Overlapping Executions
If a job takes longer than the interval between runs, you may get overlapping executions. For a job that runs every minute but sometimes takes 3 minutes:
- Minute 0: job starts
- Minute 1: new instance starts while the first is still running
- Minute 2: third instance starts
Use a lock file or flock to prevent overlap:
*/5 * * * * flock -n /tmp/myjob.lock /path/to/script.sh
The Midnight Confusion
0 0 * * * runs at midnight (00:00). 0 12 * * * runs at noon. Developers sometimes confuse hour 0 (midnight) with hour 24 (not valid in cron — hours are 0-23).
Extended Cron Syntax
Some systems support additional features:
Predefined Shortcuts (Vixie cron, many Linux systems)
@yearly # equivalent to 0 0 1 1 *
@monthly # equivalent to 0 0 1 * *
@weekly # equivalent to 0 0 * * 0
@daily # equivalent to 0 0 * * *
@hourly # equivalent to 0 * * * *
@reboot # run once at startup
Quartz/Spring Extensions
# Second field (first position)
0 0 12 * * ? # noon daily (6 fields)
# Last day of month
0 0 L * ? # midnight on the last day of every month
# Nearest weekday
0 0 15W * ? # nearest weekday to the 15th
# Nth day of week
0 0 ? * 2#3 # third Monday of the month
Building and Testing Cron Expressions
Cron syntax is simple in concept but easy to get wrong in practice. A misplaced field, a wrong day-of-week number, or a misunderstanding about how day-of-month and day-of-week interact can mean a job runs at the wrong time — or never runs at all.
Our Cron Expression Parser takes a cron expression and shows you exactly when it will fire — displaying the next several execution times, describing the schedule in plain English, and highlighting any potential issues. It is the fastest way to verify that your expression does what you intend before deploying it to production. If you have an existing crontab entry you are trying to decode, the crontab to English converter translates it into a plain-language description, and our cron cheatsheet is handy for a quick field-by-field reference.
Quick Reference
| Expression | Schedule |
|---|---|
* * * * * |
Every minute |
*/5 * * * * |
Every 5 minutes |
0 * * * * |
Every hour |
0 0 * * * |
Daily at midnight |
0 9 * * 1-5 |
Weekdays at 9 AM |
0 0 1 * * |
Monthly (1st at midnight) |
0 0 * * 0 |
Weekly (Sunday midnight) |
30 */4 * * * |
Every 4 hours at :30 |
0 9,17 * * * |
9 AM and 5 PM daily |
Once the five-field structure clicks, cron expressions become as natural as reading a clock. Practice with a few common schedules, watch out for the gotchas, and always verify your expression before deploying it.
Comments