Cron Expressions Explained: Syntax, Examples, and Common Schedules
This guide has a free tool → Open Cron Parser
Cron Expressions Explained: Syntax, Examples, and Common Schedules
A cron expression is a string that defines a schedule for automated tasks. Originally from Unix systems where the cron daemon ran scheduled jobs, cron scheduling is now embedded in virtually every part of modern infrastructure: server maintenance scripts, database backups, email campaigns, CI/CD pipelines, serverless functions, Kubernetes CronJobs, and cloud scheduling services.
Despite being decades old, cron syntax remains the dominant way to express recurring schedules in software. Understanding cron expressions means you can read, write, and debug scheduled tasks across any platform that supports them.
The Five-Field Structure
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 both = Sunday)
| | | | |
* * * * *Each field specifies when the job should run in that time unit. The fields are evaluated together: the job runs when all five conditions are simultaneously satisfied (with an important exception for day-of-month and day-of-week, covered below).
Special Characters
Cron expressions use four special characters to express complex schedules.
| Character | Name | Meaning | Example |
|---|---|---|---|
* | Asterisk | Any value / every | * * * * * runs every minute |
, | Comma | Value list | 1,15,30,45 runs at those specific values |
- | Hyphen | Range | 1-5 means 1, 2, 3, 4, and 5 |
/ | Slash | Step / interval | */15 means every 15 steps (0, 15, 30, 45) |
These can be combined within a single field. For example, 1-5,10,*/20 in the minute field means: minutes 1, 2, 3, 4, 5, 10, 20, 40, 0 (from the */20 step).
The Asterisk (*)
The asterisk means "every value in this field." In the minute field, * means every minute (0 through 59). In the hour field, * means every hour (0 through 23). Used in all five fields, * * * * * runs at every minute of every hour of every day.
The Comma (,)
The comma creates a list of specific values. 0,15,30,45 * * * * runs at minutes 0, 15, 30, and 45 of every hour. This is equivalent to running at 00:00, 00:15, 00:30, 00:45, 01:00, 01:15, and so on.
0 9,13,17 * * * runs at 9:00 AM, 1:00 PM, and 5:00 PM every day.
The Hyphen (-)
The hyphen defines a range. 1-5 in the day-of-week field means Monday through Friday (1=Monday, 5=Friday). 8-17 in the hour field means hours 8 through 17 (8 AM through 5 PM).
0 9 * * 1-5 runs at 9:00 AM every weekday.
The Slash (/)
The slash defines a step interval. */5 in the minute field means "every 5 minutes" --- values 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55.
The syntax is start/step. */5 is shorthand for 0-59/5. You can also write 10/5 to mean "starting at 10, every 5 minutes" --- values 10, 15, 20, 25, ...
*/2 * * * * runs every 2 minutes. 0 */3 * * * runs at the top of every 3rd hour: 00:00, 03:00, 06:00, 09:00, 12:00, 15:00, 18:00, 21:00.
Month Names and Day Names
Some cron implementations accept abbreviated month and day names instead of numbers. This is optional and not universally supported, but you will encounter it.
Month names (case-insensitive in most implementations):
JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC
Day of week names:
SUN, MON, TUE, WED, THU, FRI, SAT
So 0 9 * * MON-FRI is equivalent to 0 9 * * 1-5.
Six-Field Extensions (Seconds)
Some systems --- particularly AWS EventBridge, Quartz (Java), and Jenkins --- use a six-field cron format that includes seconds as the first field:
.---------------- second (0-59)
| .------------- minute (0-59)
| | .---------- hour (0-23)
| | | .------- day of month (1-31)
| | | | .---- month (1-12)
| | | | | .- day of week (0-7)
| | | | | |
* * * * * *When you copy a cron expression from Stack Overflow or documentation, check whether the platform uses 5-field or 6-field format. Pasting a 6-field expression into a 5-field system will parse incorrectly and run at unexpected times.
AWS EventBridge uses a different variant: it replaces the year field as the sixth position and uses ? for "no specific value" in either the day-of-month or day-of-week field (you must use ? in one if you specify a value in the other).
# AWS EventBridge (6 fields: minute hour dom month dow year)
0 9 ? * MON-FRI * # 9 AM every weekday
0 0 1 * ? * # Midnight on the 1st of every monthPredefined Shortcuts
Most cron implementations support named shortcuts for common schedules:
| Shortcut | Equivalent | Meaning |
|---|---|---|
@yearly or @annually | 0 0 1 1 * | Once a year at midnight January 1 |
@monthly | 0 0 1 * * | Once a month at midnight on the 1st |
@weekly | 0 0 * * 0 | Once a week at midnight on Sunday |
@daily or @midnight | 0 0 * * * | Once a day at midnight |
@hourly | 0 * * * * | Once an hour at the start of the hour |
@reboot | N/A | Once at system startup |
These shortcuts are readable and unambiguous. When @daily communicates intent as clearly as 0 0 * * *, use the shortcut.
Common Cron Schedules Reference
Here are the expressions you will write and encounter most frequently. Use this as a reference.
| Schedule | Expression | Notes |
|---|---|---|
| Every minute | * * * * * | Runs 60 times per hour |
| Every 5 minutes | */5 * * * * | 0, 5, 10, 15... |
| Every 10 minutes | */10 * * * * | 0, 10, 20, 30, 40, 50 |
| Every 15 minutes | */15 * * * * | 0, 15, 30, 45 |
| Every 30 minutes | */30 * * * * | 0 and 30 of each hour |
| Every hour | 0 * * * * | Minute 0 of every hour |
| Every 2 hours | 0 */2 * * * | 0:00, 2:00, 4:00... |
| Every 6 hours | 0 */6 * * * | 0:00, 6:00, 12:00, 18:00 |
| Every 12 hours | 0 */12 * * * | 0:00 and 12:00 |
| Daily at midnight | 0 0 * * * | 00:00 UTC every day |
| Daily at 6 AM | 0 6 * * * | 06:00 every day |
| Daily at 9 AM | 0 9 * * * | 09:00 every day |
| Daily at 11:30 PM | 30 23 * * * | 23:30 every day |
| Twice a day | 0 6,18 * * * | 6:00 AM and 6:00 PM |
| Three times a day | 0 8,12,17 * * * | 8 AM, noon, 5 PM |
| Every weekday 9 AM | 0 9 * * 1-5 | Mon through Fri |
| Every weekend midnight | 0 0 * * 6,0 | Saturday and Sunday |
| Every Monday 8 AM | 0 8 * * 1 | Weekly Monday morning |
| Every Friday 6 PM | 0 18 * * 5 | Weekly Friday evening |
| First of every month | 0 0 1 * * | Midnight on the 1st |
| 15th of every month | 0 0 15 * * | Midnight on the 15th |
| Every January 1 | 0 0 1 1 * | New Year midnight |
| Every quarter | 0 0 1 */3 * | Jan 1, Apr 1, Jul 1, Oct 1 |
| Every Sunday 3 AM | 0 3 * * 0 | Sunday early morning |
Real-World Use Cases
Database backups
Running database backups at low-traffic times prevents performance impact on production systems.
# Nightly database backup at 2 AM
0 2 * * *
# Why 2 AM? Low traffic. Gives time to complete and verify before business hours.
# The backup completes, gets compressed, and uploads to S3 before 6 AM.# Weekly full backup Sunday, daily incremental backups
0 1 * * 0 # Full backup every Sunday at 1 AM
0 1 * * 1-6 # Incremental backup Mon-Sat at 1 AMSSL certificate renewal
Let's Encrypt certificates expire after 90 days. Certbot or acme.sh needs to run regularly to check and renew certificates before they expire.
# Check twice daily (recommended by Let's Encrypt)
0 2,14 * * *
# Why twice a day? Gives multiple opportunities per day to retry if renewal fails.
# The certificate only actually renews when it's within 30 days of expiration.Log rotation and cleanup
# Rotate logs every 6 hours
0 */6 * * *
# Archive logs older than 30 days, run weekly
0 3 * * 0
# Delete temporary files daily at 4 AM
0 4 * * *Cache warming and invalidation
# Warm caches before business hours each weekday
0 7 * * 1-5
# Invalidate and rebuild search index nightly
0 1 * * *Report generation
# Daily report for previous day's activity, run at 6 AM
0 6 * * *
# Weekly report every Monday morning at 7 AM
0 7 * * 1
# Monthly summary on the 1st at 6 AM
0 6 1 * *
# Quarterly report: Jan, Apr, Jul, Oct
0 6 1 1,4,7,10 *Email digest and notifications
# Morning digest email on weekdays at 8 AM
0 8 * * 1-5
# Weekly newsletter every Thursday at 10 AM
0 10 * * 4
# Daily reminder to users who haven't logged in recently
0 16 * * *CI/CD and build pipelines
# Nightly integration test run at 3 AM
0 3 * * *
# Weekly dependency security scan every Monday
0 6 * * 1
# Monthly automated performance benchmarks
0 4 1 * *Health checks and monitoring
# Ping external services every 5 minutes
*/5 * * * *
# Generate site performance report hourly
0 * * * *
# Check for and alert on disk space every 15 minutes
*/15 * * * *The Day-of-Month and Day-of-Week Conflict
This is the most counterintuitive behavior in cron, and it catches experienced developers off guard.
When you specify a value (not *) in both the day-of-month field AND the day-of-week field, most cron implementations treat the conditions as OR, not AND. This means the job runs when either condition is true.
0 0 15 * 5You might expect this to mean "midnight on the 15th, but only if it is a Friday." What it actually means in most cron implementations: "midnight on the 15th of any month, OR midnight on any Friday." The job runs on far more days than intended.
To schedule "the 15th only if it is a Friday," you need to handle this in the script itself:
#!/bin/bash
# Cron: 0 0 15 * *
# Check inside the script if today is Friday
if [ "$(date +%u)" -eq 5 ]; then
# Run the actual task
./monthly-friday-task.sh
fiThe only safe approach for "day X if it is a day-of-week Y" is to schedule broadly and filter in the script.
Note: some extended cron implementations (like Quartz) handle this differently and do treat the combination as AND. Always verify behavior with your specific cron implementation.
Common Mistakes and How to Avoid Them
Mistake 1: Using * in the minute field instead of 0
This is the single most common cron mistake.
# WRONG: Runs every minute of hour 2 (60 executions)
* 2 * * *
# CORRECT: Runs once at 2:00 AM
0 2 * * *The expression * 2 * * * does not mean "at 2 AM." It means "every minute during the hour of 2 AM" --- which is 60 executions between 2:00 and 2:59.
Mistake 2: Sunday is 0 or 7
The day-of-week field has an inconsistency: both 0 and 7 represent Sunday in most implementations, but not all. In practice:
- 0 = Sunday (universally)
- 1 = Monday
- 2 = Tuesday
- 3 = Wednesday
- 4 = Thursday
- 5 = Friday
- 6 = Saturday
- 7 = Sunday (in most but not all implementations)
For portability, always use 0 for Sunday. Never use 7 in ranges like 0-7 --- that would mean Sunday twice, which may behave unexpectedly.
Mistake 3: Timezone assumptions
Cron jobs run in the server's local timezone unless explicitly configured otherwise. Many servers run in UTC. If you want "9 AM EST" and your server is in UTC, you need 14 * * * * during Eastern Standard Time and 13 * * * * during Eastern Daylight Time.
The daylight saving time transition is particularly problematic: a cron job set for 2 AM may run twice or not at all during DST transitions, depending on which direction the clock moves and how the cron daemon handles it.
Solutions:
- Run servers and cron jobs in UTC, then account for timezone in the application
- Use systemd timer units on Linux, which support explicit timezone specification
- Use cloud scheduling services (AWS EventBridge, Google Cloud Scheduler) that support timezone-aware schedules
Mistake 4: Multiple instances running simultaneously
If a cron job takes longer to complete than the interval between runs, multiple instances will run concurrently, potentially causing data corruption or performance issues.
# Prevent concurrent execution with flock
0 * * * * /usr/bin/flock -n /tmp/my-job.lock /path/to/my-script.shflock -n acquires a file lock and exits immediately if the lock cannot be obtained (i.e., if another instance is running). This prevents overlap.
Mistake 5: Not logging cron output
By default, cron may silently discard stdout/stderr or send it as an email. Always redirect output explicitly.
# Redirect both stdout and stderr to a log file
0 2 * * * /path/to/backup.sh >> /var/log/backup.log 2>&1
# Or discard output entirely (not recommended for production)
0 2 * * * /path/to/backup.sh > /dev/null 2>&1Mistake 6: Relative paths in cron jobs
Cron jobs run with a minimal environment. The current working directory is typically the user's home directory, and the PATH may not include common directories like /usr/local/bin.
# WRONG: relies on PATH and cwd
0 2 * * * python backup.py
# CORRECT: use absolute paths for everything
0 2 * * * /usr/bin/python3 /home/user/scripts/backup.py >> /var/log/backup.log 2>&1Platform-Specific Syntax
Standard Unix/Linux crontab
The five-field format is universal. Edit with crontab -e.
# m h dom mon dow command
0 2 * * * /usr/local/bin/backup.sh
*/15 * * * * /usr/local/bin/health-check.shGitHub Actions
GitHub Actions uses the standard 5-field format in YAML workflow files:
on:
schedule:
# Run every day at 2 AM UTC
- cron: "0 2 * * *"
# Run every Monday at 9 AM UTC
- cron: "0 9 * * 1"Note: GitHub Actions does not guarantee exact timing. High-load periods may cause delays of 15-30 minutes.
AWS EventBridge (CloudWatch Events)
AWS uses a 6-field format. The 6th field is year, and you must use ? in either the day-of-month or day-of-week field when specifying the other.
# AWS EventBridge format
# m h dom mon dow year
0 2 * * ? * # 2 AM every day
0 9 ? * MON * # 9 AM every Monday
0 0 1 * ? * # Midnight on the 1stKubernetes CronJob
Kubernetes uses the standard 5-field format:
apiVersion: batch/v1
kind: CronJob
metadata:
name: db-backup
spec:
schedule: "0 2 * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: backup
image: backup-tool:latest
restartPolicy: OnFailureVercel Cron Jobs
Vercel supports cron jobs in vercel.json using standard 5-field format:
{
"crons": [
{
"path": "/api/cleanup",
"schedule": "0 2 * * *"
},
{
"path": "/api/daily-report",
"schedule": "0 8 * * 1-5"
}
]
}Heroku Scheduler
Heroku Scheduler uses a simplified format supporting only a subset of cron syntax: every 10 minutes, every hour, or every day at a specific time. For more complex schedules, you need a third-party add-on.
Cron in Application Code
Many applications embed scheduling logic rather than using system cron. Popular libraries:
Node.js: node-cron
const cron = require("node-cron");
// Run every 15 minutes
cron.schedule("*/15 * * * *", () => {
console.log("Running health check...");
performHealthCheck();
});
// Run at 2 AM daily
cron.schedule("0 2 * * *", () => {
console.log("Running backup...");
performBackup();
});
// Run every weekday at 9 AM in a specific timezone
cron.schedule(
"0 9 * * 1-5",
() => {
sendDailyDigest();
},
{
timezone: "America/New_York",
}
);Python: APScheduler
from apscheduler.schedulers.blocking import BlockingScheduler
from apscheduler.triggers.cron import CronTrigger
scheduler = BlockingScheduler()
# Run every 15 minutes
@scheduler.scheduled_job(CronTrigger.from_crontab("*/15 * * * *"))
def health_check():
print("Running health check...")
perform_health_check()
# Run at 2 AM daily in UTC
@scheduler.scheduled_job("cron", hour=2, minute=0, timezone="UTC")
def daily_backup():
perform_backup()
# Run every weekday at 9 AM Eastern
@scheduler.scheduled_job("cron", day_of_week="mon-fri", hour=9, minute=0,
timezone="America/New_York")
def morning_digest():
send_digest()
scheduler.start()Go: robfig/cron
package main
import (
"fmt"
"github.com/robfig/cron/v3"
)
func main() {
c := cron.New(cron.WithSeconds()) // Optional: enable seconds field
// Run every 15 minutes
c.AddFunc("*/15 * * * *", func() {
fmt.Println("Health check")
})
// Run at 2 AM daily
c.AddFunc("0 2 * * *", func() {
fmt.Println("Daily backup")
})
// Run every weekday at 9 AM
c.AddFunc("0 9 * * 1-5", func() {
fmt.Println("Morning digest")
})
c.Start()
// Keep running
select {}
}Quick Reference Card
Field Allowed Values Special Characters
----------- ------------------ ------------------
Minute 0-59 * , - /
Hour 0-23 * , - /
Day of Month 1-31 * , - /
Month 1-12 or JAN-DEC * , - /
Day of Week 0-7 (0,7=Sun) * , - /
or SUN-SAT
Special characters:
* = any value
, = value list (e.g., 1,3,5)
- = range (e.g., 1-5)
/ = step (e.g., */15)
Common shortcuts:
@yearly = 0 0 1 1 *
@monthly = 0 0 1 * *
@weekly = 0 0 * * 0
@daily = 0 0 * * *
@hourly = 0 * * * *
Time units in seconds (for arithmetic):
1 minute = 60
1 hour = 3600
1 day = 86400
1 week = 604800Parse and Validate Cron Expressions with ToolBox
The Cron Parser on ToolBox translates any cron expression into plain English and shows you the next several execution times. Paste 0 9 * * 1-5 and immediately see "At 9:00 AM, Monday through Friday" along with the upcoming dates.
This is invaluable when inheriting cron jobs you did not write, reviewing a teammate's scheduled task, or just double-checking that your expression does what you intend.
Both tools run entirely in your browser. No server, no signup, no delay.
---
Bookmark the Cron Parser --- you will reach for it every time you need to verify a cron schedule.
Related Tools
Free, private, no signup required
JSON Formatter
JSON formatter and validator online - format, beautify, and validate JSON data instantly in your browser
Regex Tester
Free online regex tester - test and debug regular expressions with live matching and highlights
Text Diff Checker
Free online text diff checker - compare two texts and see the differences highlighted line by line
Code Formatter
Free online code formatter - beautify and format JavaScript, CSS, HTML, and more
You might also like
Want higher limits, batch processing, and AI tools?