Cron Expressions Explained, With Examples
Cron is small, weird and easy to get wrong. Here's the field layout, the special characters, the gotchas that bite in production, and a copy-paste table of the expressions you actually need.
The 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; 7 = Sunday too)
│ │ │ │ │
* * * * * command
Quartz/Spring add a seconds field at the start, making it six. Some implementations add a year field at the end. Always confirm which dialect your scheduler uses.
The special characters
*— any value,— a list:1,15,30-— a range:9-17/— a step:*/5means every 5?— "no specific value" (Quartz only, used in day-of-month / day-of-week)L— last (Quartz; e.g.Lin day-of-month = last day)#— nth weekday of month (Quartz;6#3= third Friday)
The gotcha that catches everyone
In standard Unix cron, day-of-month and day-of-week are OR'd, not AND'd. If you write:
0 9 1 * MON # 9am, on the 1st of the month, OR on Monday
…it fires every Monday and on the 1st of every month. To get "9am on the 1st only if it's a Monday", you need application-level logic — pure cron can't express it.
*.Timezones
Linux cron uses the system timezone (often UTC on servers, local time on workstations). Managed schedulers are different:
- Kubernetes CronJob: UTC by default;
spec.timeZoneavailable since v1.27. - AWS EventBridge: UTC. Use the
cron(min hour day-of-month month day-of-week year)6-field syntax. - GCP Cloud Scheduler: configurable timezone per job.
Always write the timezone next to the expression in code comments. "Midnight" without a timezone is a future incident.
Reference table
The expressions you actually need:
* * * * * every minute
*/5 * * * * every 5 minutes
0 * * * * top of every hour
0 */2 * * * every 2 hours
30 9 * * * every day at 09:30
0 9 * * 1-5 weekdays at 09:00
0 9 * * MON-FRI (same, named form)
0 0 * * 0 every Sunday at midnight
0 0 1 * * 1st of every month at midnight
0 0 1 1 * midnight on January 1st
15 10 * * 1 Mondays at 10:15
0 22 * * 1-5 weekdays at 22:00 (nightly job)
0 0 * * 6,0 every Saturday and Sunday at midnight
*/15 9-17 * * 1-5 every 15 min, business hours, weekdays
Common mistakes
- Wrong field count. Pasting a 6-field Quartz expression into Linux cron silently misinterprets the meaning.
- Forgotten timezone. A "daily report at 8am" runs at 3am for half the world.
- DST drift. An expression like
30 2 * * *either runs twice or skips entirely on DST transitions in local-time schedulers. - Overlapping runs. A job that takes 6 minutes scheduled every 5 will pile up. Use a lockfile, or a scheduler with
concurrencyPolicy: Forbid. - Step ambiguity.
*/7in the hour field doesn't fire every 7 hours — it fires at hours 0, 7, 14, and 21, then jumps back to 0. Use explicit lists for non-divisors.
FAQ
Why is my cron job not running at midnight on the 1st?
* values fires more often than most people expect. Check your timezone too — server time may not be what you assume.Does cron use UTC or local time?
cron uses the system timezone. Managed schedulers like AWS EventBridge and Kubernetes CronJob (pre-1.27) default to UTC. Always document the timezone alongside the expression.What's the difference between 5-field and 6-field cron?
How do I run something every 30 seconds?
*/30 * * * * ?, or run a 1-minute cron that itself triggers two runs 30 seconds apart.