Cron expressions pack a scheduling language into five space-separated fields. They originated in Unix cron (1975) and haven’t changed much since — the same syntax drives crontab on Linux, Kubernetes CronJobs, AWS EventBridge rules, GitHub Actions schedules, and dozens of other systems. Once you internalize the five fields, you can read any cron expression on sight.
The five fields
A standard cron expression has five fields, left to right:
┌───────────── minute (0–59)
│ ┌─────────── hour (0–23)
│ │ ┌───────── day of month (1–31)
│ │ │ ┌─────── month (1–12 or JAN–DEC)
│ │ │ │ ┌───── day of week (0–7, 0 and 7 are Sunday, or SUN–SAT)
│ │ │ │ │
* * * * *Each field constrains when the job fires. A job runs when all fivefields match the current time (with one exception involving day-of-month and day-of-week, covered below). The smallest resolution is one minute — cron doesn’t do seconds.
Special characters
Four characters give you everything beyond “run at exactly this time”:
*— matches every value.* * * * *means every minute of every hour of every day.,— value list.1,15in the minute field means minute 1 and minute 15.-— range.1-5in the day-of-week field means Monday through Friday (assuming 0=Sunday)./— step.*/5in the minute field means every 5 minutes (0, 5, 10, 15, …).10-30/5means every 5 minutes between minute 10 and minute 30.
These compose freely. 0,30 9-17 * * 1-5 means “at minute 0 and 30, during hours 9 through 17, Monday through Friday.” That’s every half hour during business hours on weekdays.
Common schedules
A handful of patterns cover most real-world scheduling needs. Here are the ones you’ll see over and over:
# Every 5 minutes
*/5 * * * *
# Every hour, on the hour
0 * * * *
# Midnight daily
0 0 * * *
# 2:30 AM daily (common for DB backups)
30 2 * * *
# Every Monday at 9 AM
0 9 * * 1
# First of every month at midnight
0 0 1 * *
# Weekdays at 8 AM
0 8 * * 1-5
# Every 15 minutes during business hours, weekdays
*/15 9-17 * * 1-5
# Twice a day: 6 AM and 6 PM
0 6,18 * * *
# Quarterly: Jan 1, Apr 1, Jul 1, Oct 1 at midnight
0 0 1 1,4,7,10 *A quick sanity check: if the minute field is *, the job runs every minutethat the other fields match. Forgetting to pin the minute is the most common cron mistake — you wanted a daily job at midnight, you wrote * 0 * * *, and it ran 60 times in the midnight hour.
The day-of-month / day-of-week gotcha
Standard cron has a special rule: when both the day-of-month and day-of-week fields are restricted (not *), the job runs if either field matches. This is OR logic, not AND.
# Runs on the 15th AND every Friday — not "the 15th if it's a Friday"
0 0 15 * 5This trips people up because every other field combination uses AND logic. If you want “the first Monday of every month,” plain cron can’t express that directly. The usual workaround is to schedule it for every Monday and add a shell guard:
# First Monday of every month
0 9 * * 1 [ $(date +\%d) -le 7 ] && /path/to/script.shSome implementations (Quartz, Spring, certain cloud schedulers) swap the OR for AND or add a sixth/seventh field. Always check your platform’s documentation.
Timezones
Classic cron evaluates expressions in the system’s local timezone. On a server set to UTC, 0 9 * * * means 9 AM UTC. On a laptop set to America/New_York, the same expression means 9 AM Eastern.
This gets weird around DST transitions. When clocks spring forward and 2:00 AM doesn’t exist, a job scheduled at 2:30 AM either skips entirely or runs at 3:00 AM — behavior varies by implementation. When clocks fall back and 1:00 AM happens twice, some implementations run the job twice, some run it once.
The safe default: schedule jobs in UTC. If a job must run at a local time (e.g., “send the daily digest at 9 AM New York”), use a scheduler that supports explicit timezone configuration. Kubernetes CronJobs added a timeZone field in v1.27. AWS EventBridge rules have always supported it. GitHub Actions cron triggers run in UTC with no timezone option.
crontab vs Kubernetes vs cloud schedulers
The expression syntax is the same across platforms, but the surrounding behavior differs enough to cause surprises.
Linux crontab
Edit with crontab -e. Each line is a schedule + command. Runs in a minimal shell environment — your PATH, environment variables, and shell aliases aren’t inherited. If a script works interactively but fails in cron, check the environment first. The MAILTO variable controls where stdout/stderr goes (defaults to the user’s local mailbox).
Kubernetes CronJobs
Creates a new Job (and Pod) on each trigger. If the previous run is still going, concurrencyPolicy controls what happens: Allow (default, runs concurrently), Forbid (skips the new run), or Replace (kills the old run). The startingDeadlineSeconds field matters for clusters that might miss a scheduled tick — if the controller was down and the deadline has passed, it skips the run instead of firing late. If it misses 100 or more schedules, the CronJob stops scheduling entirely until manually triggered.
Cloud schedulers
AWS EventBridge uses either cron or rate expressions. EventBridge cron has six fields (adds a year field) and uses ? instead of * for “no specific value” in the day-of-month or day-of-week field (one must be ?). Google Cloud Scheduler uses standard 5-field cron with an explicit timezone parameter. Azure Functions use NCrontab with six fields (adds seconds as the first field).
The lesson: verify the exact syntax your platform expects. The expression 0 12 * * * is valid everywhere, but the surrounding configuration (timezone handling, missed run behavior, concurrency) varies enough to matter.
Debugging cron schedules
When a cron job doesn’t fire, work through this checklist:
- Check the minute field. Is it
*when it should be0? Is it0when it should be*? - Confirm the timezone. What timezone is the scheduler running in? Is it UTC or local?
- Check day-of-week indexing.Is Sunday 0 or 7? Is Monday 1? Does the system use 0–6 or 1–7?
- Test the expression. Paste it into a cron expression tool and look at the next 5 run times. If the next run is in 2027, something is wrong.
- Check the environment. For crontab, log the output: append
>> /tmp/cron.log 2>&1to the command. Scripts that work interactively often fail in cron’s stripped-down shell.
Extended syntax: non-standard characters
Some implementations add extra characters beyond the standard four. These are not universal:
L— last.Lin day-of-month means the last day of the month.5Lin day-of-week means the last Friday. Supported in Quartz, Spring, and some cloud schedulers.W— nearest weekday.15Wmeans the nearest weekday to the 15th. Quartz only.#— nth occurrence.1#1in day-of-week means the first Monday of the month. Solves the “first Monday” problem without a shell guard. Quartz and Spring only.?— no specific value. Used by Quartz and AWS EventBridge in the day-of-month or day-of-week field.
If you’re writing expressions that need to work across multiple platforms, stick to the four standard characters: *, ,, -, /.
Quick reference
Expression Description
────────────────── ─────────────────────────────────────
* * * * * Every minute
*/5 * * * * Every 5 minutes
0 * * * * Every hour
0 0 * * * Daily at midnight
0 0 * * 0 Every Sunday at midnight
0 0 1 * * First of every month
0 0 1 1 * Once a year (Jan 1 at midnight)
30 4 * * 1-5 Weekdays at 4:30 AM
0 */2 * * * Every 2 hours
0 9-17 * * * Every hour from 9 AM to 5 PM
0 0 15 * * 15th of every month at midnight
*/10 * * * 1-5 Every 10 minutes on weekdaysNeed to build or decode a cron expression? The Cron Expression Builder lets you construct expressions visually, see a plain-English description as you go, and preview the next run times — so you know exactly when your job will fire before you deploy it.