Cron Expression Cheat Sheet With 30 Examples

· by webmaster

A cron expression is five short fields that decide when a job runs. They are easy to write and easy to get subtly wrong, and a wrong one tends to fail without an error at 2 a.m. rather than in code review. This cheat sheet gives you the field order, the meaning of every special character, 30 copy-ready examples with plain-English descriptions, and the differences between the schedulers you are likely to use. If you prefer clicking to typing, the cron expression generator builds the expression and shows what it means.

Cron has five fields in a fixed order

┌───────────── minute (0-59)
│ ┌───────────── hour (0-23)
│ │ ┌───────────── day of month (1-31)
│ │ │ ┌───────────── month (1-12 or JAN-DEC)
│ │ │ │ ┌───────────── day of week (0-6, Sunday = 0, or SUN-SAT)
│ │ │ │ │
* * * * *  command-to-run

A job fires when the minute, hour and month all match and the day condition matches. The day condition has a twist, covered below. Whitespace separates the fields, and everything after the fifth field is the command, so a stray space inside a field breaks the whole line.

Most implementations accept 7 as Sunday and three-letter names for months and weekdays. Keep ranges numeric if the expression has to move between systems. The original Vixie cron man page says ranges of names are not allowed.

Cron special characters and what they mean

CharacterMeaningExampleReads as
*Every value* * * * *Every minute
,List0 9,17 * * *At 09:00 and 17:00
-Range0 9-17 * * *Every hour from 09:00 to 17:00
/Step*/15 * * * *Every 15 minutes
?No specific value (Quartz, AWS)0 12 ? * MONNoon on Mondays
LLast (Quartz, AWS)0 0 L * ?Midnight on the last day of the month
WNearest weekday (Quartz, AWS)0 9 15W * ?09:00 on the weekday nearest the 15th
#Nth weekday (Quartz, AWS)0 9 ? * MON#209:00 on the second Monday

Standard Unix cron supports only the first four. ?, L, W and # come from Quartz, and AWS EventBridge adopted them. A Linux crontab or Kubernetes CronJob rejects them.

A step counts from the start of the range, not from the last run. */15 means 0, 15, 30, 45, and 5-59/15 means 5, 20, 35, 50. For example, */7 in the minute field does not mean “every seven minutes”. It fires at 0, 7, 14 and so on up to 56, then at 0 again at the top of the next hour, with a four-minute gap in between. A step that does not divide evenly into the field’s range rarely gives the schedule you intended.

30 cron expression examples

#ExpressionMeaning
1* * * * *Every minute
2*/5 * * * *Every 5 minutes
3*/10 * * * *Every 10 minutes
4*/15 * * * *Every 15 minutes
50,30 * * * *Every 30 minutes, on the hour and half hour
60 * * * *Every hour, at minute 0
730 * * * *Every hour, at minute 30
80 */2 * * *Every 2 hours (00:00, 02:00, 04:00 …)
90 */6 * * *Every 6 hours (00:00, 06:00, 12:00, 18:00)
100 0 * * *Daily at midnight
110 9 * * *Daily at 09:00
1230 18 * * *Daily at 18:30
1315 2 * * *Daily at 02:15 (a typical backup slot)
140 9,17 * * *Daily at 09:00 and 17:00
150 9-17 * * *Every hour from 09:00 to 17:00, every day
16*/30 9-17 * * 1-5Every 30 minutes between 09:00 and 17:30, Monday to Friday
170 9 * * 1-5Weekdays at 09:00
180 22 * * 1-5Weekdays at 22:00
190 10 * * 0,6Saturdays and Sundays at 10:00
200 0 * * 0Sundays at midnight
210 3 * * 0Sundays at 03:00 (weekly maintenance)
220 8 * * 1Mondays at 08:00
230 0 1 * *First day of every month at midnight
240 6 1,15 * *1st and 15th of every month at 06:00
250 0 28-31 * *Days 28 to 31 at midnight (fires up to 4 times, see note)
260 0 1 */3 *First day of every quarter (Jan, Apr, Jul, Oct) at midnight
270 0 1 1 *1 January at midnight (yearly)
280 0 25 12 *25 December at midnight
290 0 L * ?Last day of every month at midnight (Quartz, AWS only)
300 9 ? * MON#1First Monday of every month at 09:00 (Quartz, AWS only)

Example 25 is the standard-cron approximation of “last day of the month”. It runs on the 28th, 29th, 30th and 31st whenever those dates exist, so up to 4 times. To fire only on the true last day, add a shell test:

0 0 28-31 * * [ "$(date -d tomorrow +\%d)" = "01" ] && /opt/scripts/month-end.sh

This uses GNU date, and the -d tomorrow flag does not exist on macOS or BSD. The % is escaped because a bare % in a crontab line means “newline, and everything after is stdin”. Escape every % in a crontab command. When a cron job runs only the first half of its command with no error, an unescaped % is the usual cause.

Cron nicknames replace the five fields

Vixie cron, cronie and Kubernetes also accept @hourly, @daily, @weekly, @monthly, @yearly and @reboot in place of the five fields. GitHub Actions and AWS EventBridge do not. @reboot is the odd one out because it is not a time: it runs once when the cron daemon starts.

The day-of-month vs day-of-week OR rule

When both the day-of-month and day-of-week fields are restricted (neither is *), standard cron runs the job when either one matches, not when both do.

0 12 1-7 * 1

You might read this as “noon on the first Monday of the month”. Cron reads it as “noon on days 1 to 7, and also noon on every Monday”, which is up to eleven runs a month instead of one. This bug ships easily because the job does run on the first Monday. The schedule looks right until someone reads the logs and sees the report went out six extra times.

In plain cron, restrict one day field and test the other in the command:

0 12 1-7 * * [ "$(date +\%u)" = "1" ] && /opt/scripts/first-monday.sh

date +%u prints 1 for Monday through 7 for Sunday. Quartz and AWS avoid the problem by requiring ? in one of the two fields, and they offer MON#1 for this exact case. Here the Quartz syntax is clearer than standard cron.

Cron time zones depend on the host

Cron uses the zone the daemon or service is configured for, and that varies by platform:

  • Linux crontab: the system time zone, usually UTC on cloud servers. Cronie and some other implementations honor a CRON_TZ=Europe/Berlin line at the top of the crontab, so check man 5 crontab on your distribution.
  • Kubernetes CronJob: the kube-controller-manager’s zone by default, which is nearly always UTC. Set spec.timeZone: "Asia/Jakarta" on a recent cluster to be explicit.
  • GitHub Actions: always UTC, with no override.
  • AWS EventBridge Scheduler: UTC by default, with a per-schedule time zone option. Classic EventBridge rules are UTC only.
  • Quartz: the JVM default zone unless you call inTimeZone() on the trigger.

Daylight saving time (DST) adds a second problem. On a naive scheduler, a job at 30 2 * * * is skipped on the night the clocks spring forward, because 02:30 never happens. It runs twice when the clocks fall back, because 02:30 happens twice. Vixie cron and cronie compensate for this on fixed-time jobs, but Kubernetes and most cloud schedulers make no such promise.

Schedule in UTC wherever you can. If you must use a local zone that observes DST, keep jobs out of the hour that gets skipped or repeated. To convert between “9 AM in Jakarta” and the UTC value the server needs, use the Unix timestamp converter, which shows both zones side by side.

Differences between cron schedulers

SchedulerFieldsSecondsYear? L W #SundayNotes
Linux crontab (Vixie, cronie)5NoNoNo0 or 7Nicknames supported; % must be escaped
Kubernetes CronJob5NoNoNo0 or 7Nicknames supported; spec.timeZone available
GitHub Actions5NoNoNo0 or 7UTC only; shortest interval is 5 minutes; runs may be delayed or dropped under load
AWS EventBridge6NoYes (last field)Yes1 or SUNcron(0 12 * * ? *); one of day fields must be ?
Quartz (Java, Spring)6 or 7Yes (first field)Optional (last)Yes1 or SUNOne of day fields must be ?

The same schedule looks different in each:

# Weekdays at 09:30
30 9 * * 1-5                 # crontab, Kubernetes, GitHub Actions
cron(30 9 ? * MON-FRI *)     # AWS EventBridge
0 30 9 ? * MON-FRI           # Quartz (seconds first)

Watch for two differences. In AWS and Quartz, numeric weekdays run 1 to 7 starting at Sunday, so 1-5 means Sunday to Thursday. Use day names there and the numbering stops mattering. Quartz also puts seconds first, so a five-field crontab expression pasted into Quartz shifts every field one place to the right. The result is either an error or a schedule unrelated to the one you meant.

Check the cron expression before you commit it

Read the expression back in English before it goes near production. The cron generator does that for you and makes the day-of-week OR rule and odd step sizes visible. Check which zone the host uses instead of assuming. It is usually UTC, while the schedule in the ticket was often written in local time. When you move an expression from a crontab to EventBridge or Quartz, translate it field by field instead of pasting it, because the field count and the weekday numbering both change and neither platform warns you about the shift.

Tools used in this guide

Copied to clipboard!