The Cron Expression Cheatsheet Every Developer Needs
Cron expressions are one of those things where you think you know them until you're staring at 0 5 * * 1-5 at 11pm trying to figure out why your backup job fired on a Sunday.
This is the reference I keep open. Bookmark it, share it with your team, print it out — whatever works.
The Anatomy of a Cron Expression
Standard cron uses 5 fields. Some systems (like many job schedulers) add a 6th for seconds.
┌───────── minute (0–59)
│ ┌─────── hour (0–23)
│ │ ┌───── day of month (1–31)
│ │ │ ┌─── month (1–12 or JAN–DEC)
│ │ │ │ ┌─ day of week (0–7, Sun=0 or 7, or SUN–SAT)
│ │ │ │ │
* * * * *
If your scheduler supports 6 fields with seconds:
┌─────────── second (0–59)
│ ┌─────── minute (0–59)
│ │ ┌───── hour (0–23)
│ │ │ ┌─── day of month (1–31)
│ │ │ │ ┌─ month (1–12)
│ │ │ │ │ ┌ day of week (0–7)
│ │ │ │ │ │
* * * * * *
Systems like Kubernetes CronJobs, node-cron, and AWS EventBridge use 5 fields. Systems like Quartz Scheduler (Java) or some cloud functions use 6.
Special Characters
| Character | Meaning | Example |
|---|---|---|
* | Any / every value | * * * * * — runs every minute |
, | List of values | 0 9,17 * * * — runs at 9am and 5pm |
- | Range | 0 9-17 * * * — runs every hour from 9am to 5pm |
/ | Step / interval | */15 * * * * — every 15 minutes |
? | No specific value (day fields only) | 0 0 1 * ? — midnight on 1st, any weekday |
L | Last (day fields, some systems only) | 0 0 L * * — last day of month |
W | Nearest weekday (some systems only) | 0 0 15W * * — nearest weekday to 15th |
# | Nth weekday (some systems only) | 0 0 * * 1#2 — 2nd Monday of month |
The Cheatsheet
Every N Minutes/Hours
*/5 * * * * # Every 5 minutes
*/15 * * * * # Every 15 minutes
*/30 * * * * # Every 30 minutes
0 */2 * * * # Every 2 hours (on the hour)
0 */6 * * * # Every 6 hours
Daily Jobs
0 0 * * * # Midnight (00:00) every day
0 2 * * * # 2am every day (classic backup time)
0 8 * * * # 8am every day
0 8,20 * * * # 8am and 8pm every day
30 23 * * * # 11:30pm every day
Weekday vs Weekend
0 9 * * 1-5 # 9am Monday through Friday
0 9 * * MON-FRI # Same thing, using names
0 10 * * 6,0 # 10am on Saturday and Sunday
0 10 * * SAT,SUN # Same thing
0 9 * * 1 # 9am every Monday
Heads up: Day-of-week
0and7both mean Sunday in most implementations. Don't rely on7being universally supported.
Monthly and Quarterly
0 0 1 * * # Midnight on the 1st of every month
0 0 1,15 * * # Midnight on 1st and 15th
0 0 L * * # Last day of month (if supported)
0 0 1 */3 * # Quarterly: Jan, Apr, Jul, Oct
0 0 1 1,4,7,10 * # Same thing but explicit
Yearly
0 0 1 1 * # Midnight on January 1st (Happy New Year jobs)
0 0 * * * # Not yearly, don't confuse this — it's daily midnight
Every N Seconds (6-field syntax)
*/10 * * * * * # Every 10 seconds (node-cron, Quartz)
*/30 * * * * * # Every 30 seconds
0 * * * * * # Every minute at second 0
Named Shortcuts
Most cron implementations support these shortcuts:
| Shortcut | Equivalent | Meaning |
|---|---|---|
@yearly / @annually | 0 0 1 1 * | Once a year, January 1st, midnight |
@monthly | 0 0 1 * * | Once a month, first day, midnight |
@weekly | 0 0 * * 0 | Once a week, Sunday midnight |
@daily / @midnight | 0 0 * * * | Once a day, midnight |
@hourly | 0 * * * * | Once an hour, on the hour |
@reboot | — | Run once at startup (crontab only) |
Month and Day Name Aliases
You can use 3-letter names instead of numbers:
Months: JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC
Days: SUN MON TUE WED THU FRI SAT
0 9 * JAN-MAR MON-FRI # 9am weekdays in Q1
0 0 25 DEC * # Christmas midnight
Real-World Examples
These are patterns I've actually used in production:
# Database backup every night at 2am
0 2 * * *
# Clear temp files every Sunday at 3am
0 3 * * SUN
# Send weekly digest emails Monday morning
0 8 * * MON
# Check stock prices every 5 minutes on weekdays, market hours only
*/5 9-16 * * 1-5
# Monthly invoice generation (1st of month, 7am)
0 7 1 * *
# Expire old sessions every 15 minutes
*/15 * * * *
# Generate sitemap nightly
0 1 * * *
# Healthcheck ping to external service every minute
* * * * *
# Rotate logs at midnight, keep 30 days of history
0 0 * * *
The Timezone Trap
This is where most production incidents live. Cron runs in the system timezone of the machine running it unless you specify otherwise.
The classic failure: your job is configured as 0 2 * * * thinking "2am server time" but the server is in UTC+3. It runs at 5am local time. Or you deployed to a new region, timezone changed, and suddenly your "daily midnight" job runs at 9am.
Always declare timezone explicitly when the schedule matters to end users:
# On Linux (user crontab or system cron)
CRON_TZ="America/Sao_Paulo"
0 2 * * * /opt/scripts/backup.sh
# Or per-job (some crond implementations)
0 2 * * * TZ="America/New_York" /opt/scripts/report.sh
For Kubernetes CronJobs, use the .spec.timeZone field (K8s 1.25+):
spec:
schedule: "0 2 * * *"
timeZone: "America/Sao_Paulo"
For GitHub Actions:
on:
schedule:
- cron: "0 5 * * *" # This is always UTC. Always.
GitHub Actions cron is always UTC. Always. I've been burned by this twice.
Testing Your Expressions
Before you deploy, test your expression:
- Cronping Cron Expression Generator — validate expressions and see next execution times
python3 -c "import croniter; c = croniter.croniter('*/5 9-17 * * 1-5'); print([c.get_next() for _ in range(5)])"— programmaticnode -e "const cron = require('cron-parser'); console.log(cron.parseExpression('*/5 * * * *').next().toDate())"— Node.js
Most importantly: run it with monitoring from day one. If a cron job doesn't report in, you want to know.
Connecting to Cronping
Once you've got your expression right, wrap your jobs with Cronping to make sure they actually run — and that you know when they don't.
#!/bin/bash
# db-backup.sh
PING_URL="https://ping.cronping.com/YOUR_PING_KEY"
# Signal start
curl -fsS -m 10 --retry 5 -o /dev/null "$PING_URL/start"
# Run the job
pg_dump -h localhost mydb | gzip > /backups/mydb_$(date +%F).sql.gz
EXIT_CODE=$?
# Signal completion with exit code (0=success, anything else=fail)
curl -fsS -m 10 --retry 5 -o /dev/null "$PING_URL/$EXIT_CODE"
Cronping knows your cron expression, so if a ping doesn't arrive when expected, it alerts you. No more "the backup hasn't run in 3 weeks and we only noticed when we needed to restore."
Quick Reference Card
Save this part:
* * * * *
│ │ │ │ └─ day of week (0=Sun, 7=Sun, 1-6=Mon-Sat)
│ │ │ └──── month (1-12, or JAN-DEC)
│ │ └─────── day of month (1-31)
│ └────────── hour (0-23)
└───────────── minute (0-59)
Special chars: * (any) , (list) - (range) / (step)
Every minute: * * * * *
Every 5 min: */5 * * * *
At midnight: 0 0 * * *
At 2am daily: 0 2 * * *
Weekdays 9am: 0 9 * * 1-5
Sundays 3am: 0 3 * * 0
1st of month: 0 0 1 * *
That's it. Print it, paste it in your team wiki, or just keep this tab open — your call.