Cron Expressions Explained: Syntax, Examples, and Tips
June 10, 2026 · 8 min read
Cron Expressions Explained: Syntax, Examples, and Tips
Cron is the time-based job scheduler found on virtually every Unix-like system. It's been running scheduled tasks since 1975, and despite decades of newer alternatives, cron and cron-inspired syntax remains the universal language for "run this at this time." Understanding cron expressions is a fundamental skill for any developer who works with servers, CI/CD, or cloud infrastructure.
Use the Crontab Generator to build expressions visually, and the Cron Parser to decode what an existing expression actually means.
The 5-Field Syntax
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 both Sunday)
│ │ │ │ │
* * * * *
Every field must contain a valid value, a wildcard, or a special expression. The job runs whenever all five fields match the current time.
0 9 * * 1-5
│ │ │ │ └── Monday through Friday
│ │ │ └──── any month
│ │ └────── any day of month
│ └──────── 9 AM (09:00)
└────────── at minute 0
This runs at 9:00 AM every weekday.
Some environments (Quartz scheduler, AWS EventBridge, some crontab implementations) use a 6-field format that adds seconds as the first field, or a 7-field format that adds a year field. Standard Unix cron uses 5 fields.
Special Characters
* — Wildcard (Every)
Matches every valid value for that field.
* * * * * → runs every minute of every hour of every day
0 * * * * → runs at the start of every hour
, — List
Matches multiple specific values.
0 9,17 * * * → 9:00 AM and 5:00 PM every day
0 0 1,15 * * → midnight on the 1st and 15th of each month
0 0 * * 1,3,5 → midnight on Monday, Wednesday, Friday
- — Range
Matches a contiguous range of values.
0 9 * * 1-5 → 9:00 AM weekdays (Monday to Friday)
0 9-17 * * * → every hour from 9 AM to 5 PM
*/1 8-20 * * * → every minute from 8 AM to 8 PM
/ — Step
Matches every Nth value within a range. */5 means "every 5 units", 0/15 means "starting at 0, every 15 units".
*/15 * * * * → every 15 minutes
0 */4 * * * → every 4 hours (at minute 0: 00:00, 04:00, 08:00, ...)
*/5 9-17 * * * → every 5 minutes between 9 AM and 5 PM
? — No Specific Value
Used in day-of-month or day-of-week fields when you want to specify one but not the other. Standard Unix cron doesn't use ?, but Quartz, Spring Scheduler, and AWS EventBridge do.
0 9 15 * ? → 9 AM on the 15th, regardless of what day of the week it is
0 9 ? * 1 → 9 AM every Monday, regardless of the day of month
In standard Unix cron, use * where you'd use ? in Quartz.
L — Last
Used in Quartz and some extended cron implementations. L in day-of-month means the last day of the month. 5L in day-of-week means the last Friday of the month.
0 0 L * ? → midnight on the last day of every month (Quartz)
0 0 ? * 5L → midnight on the last Friday of every month (Quartz)
W — Nearest Weekday
W in day-of-month means the nearest weekday to that date. 15W means the nearest weekday to the 15th.
0 9 15W * ? → 9 AM on the nearest weekday to the 15th (Quartz)
Common Examples
Run Every N Minutes
*/5 * * * * → every 5 minutes
*/10 * * * * → every 10 minutes
*/15 * * * * → every 15 minutes
*/30 * * * * → every 30 minutes
Run at Specific Times
0 9 * * * → 9:00 AM every day
30 14 * * * → 2:30 PM every day
0 0 * * * → midnight every day
0 6,12,18 * * * → 6 AM, noon, and 6 PM every day
Run on Specific Days
0 9 * * 1-5 → 9 AM weekdays only
0 10 * * 6,0 → 10 AM on weekends (Saturday=6, Sunday=0)
0 9 * * 1 → 9 AM every Monday
0 0 * * 0 → midnight every Sunday
Run on Specific Dates
0 0 1 * * → midnight on the 1st of every month
0 0 1 1 * → midnight on January 1st (New Year)
0 0 1,15 * * → midnight on the 1st and 15th of each month
30 4 1,15 * * → 4:30 AM on the 1st and 15th
Run at Startup
This is not a time-based expression but a special cron keyword:
@reboot /path/to/script.sh → run once when the system starts
Other special strings in many cron implementations:
@hourly → 0 * * * *
@daily → 0 0 * * *
@weekly → 0 0 * * 0
@monthly → 0 0 1 * *
@yearly → 0 0 1 1 *
The Timezone Warning
This is the most common source of cron bugs in production. Standard cron runs in the server's local timezone. If your server is in UTC (as most cloud VMs are by default) and you write 0 9 * * *, that's 9 AM UTC — which is 4 AM Eastern, 1 AM Pacific, or 2 PM IST.
# Check your server's timezone
timedatectl
# or
date +%Z
For user-facing scheduled jobs, always either:
- Run your server in UTC and document that all cron times are UTC
- Use a scheduling system that accepts IANA timezone identifiers
- Mentally convert your target time to UTC before writing the expression
In systemd timers, you can specify timezone explicitly:
[Timer]
OnCalendar=Mon..Fri 09:00 America/New_York
GitHub Actions and most cloud schedulers also accept TZ settings or use UTC by default and document it clearly.
Where Cron Expressions Are Used
crontab -e
The standard way to edit cron jobs on Linux/macOS:
crontab -e # edit current user's crontab
crontab -l # list current user's crontab
sudo crontab -e # edit root's crontab
crontab -u www-data -e # edit another user's crontab
Each line in the crontab is a cron expression followed by the command to run:
*/30 * * * * /usr/bin/python3 /home/user/scripts/sync.py >> /var/log/sync.log 2>&1
0 2 * * * /usr/bin/pg_dump mydb > /backups/db-$(date +\%Y\%m\%d).sql
systemd Timers
Modern Linux systems increasingly use systemd timers as a cron replacement. They provide better logging, dependency handling, and timezone support:
# /etc/systemd/system/my-job.timer
[Unit]
Description=Run my-job daily
[Timer]
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.target
systemctl enable my-job.timer
systemctl start my-job.timer
systemctl list-timers --all
GitHub Actions
GitHub Actions uses 5-field cron syntax in UTC:
on:
schedule:
- cron: '0 8 * * 1-5' # 8 AM UTC, Monday to Friday
- cron: '0 0 1 * *' # midnight UTC on the 1st of each month
Note: GitHub Actions cron jobs can be delayed by up to a few minutes during high-traffic periods. They're not suitable for second-level precision.
Kubernetes CronJob
apiVersion: batch/v1
kind: CronJob
metadata:
name: daily-report
spec:
schedule: "0 2 * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: report
image: my-report-image
Kubernetes CronJob also supports a timeZone field in newer versions:
spec:
timeZone: "America/New_York"
schedule: "0 9 * * 1-5"
Cloud Schedulers
AWS EventBridge Scheduler, Google Cloud Scheduler, and Azure Logic Apps all support cron expressions (sometimes with 6-field Quartz syntax). Always check the documentation for whether they use standard 5-field or Quartz 6-field syntax.
Testing Cron Expressions Before Deploying
Never deploy a cron expression without verifying it first. Mistakes like 0 9 * * * vs 9 0 * * * (9 AM vs 12:09 AM) are easy to make and hard to notice.
The Cron Parser shows you the next N scheduled run times for any expression, which immediately reveals if the schedule is what you intended. The Crontab Generator lets you build expressions from natural-language inputs and see the schedule before committing it.
For command-line testing, crontab -e changes take effect immediately, but you can also use systemd-analyze calendar to test systemd OnCalendar expressions:
systemd-analyze calendar "Mon..Fri 09:00"
# Shows next trigger times and UTC equivalent
Quick Reference Card
| Expression | Meaning |
|---|---|
* * * * * |
Every minute |
0 * * * * |
Every hour |
0 9 * * * |
9 AM daily |
0 9 * * 1-5 |
9 AM weekdays |
*/15 * * * * |
Every 15 minutes |
0 0 1 * * |
Midnight, 1st of month |
0 0 * * 0 |
Midnight every Sunday |
30 4 1,15 * * |
4:30 AM on 1st and 15th |
0 0 1 1 * |
Midnight Jan 1st (annually) |
@daily |
Same as 0 0 * * * |
@reboot |
Once at system startup |
Use the Crontab Generator to build any schedule visually and export the expression, or the Cron Parser to decode an existing one into plain English with upcoming run times.