DevKitLab Logo DevKitLab
Cron / crontab / Scheduling / DevOps

Cron Schedule Examples: Every Minute, Hour, Day, Week, and Month

You know the cadence you want — every 5 minutes, weekdays at 9, the 1st — and just need the line. Copy-ready cron lines for the common cases, plus the few where the obvious guess is wrong.

Most cron guides start from the expression and teach you to read it. This one starts from the other end: you already know the schedule you want in plain English — every five minutes, every weekday at 9, the first of the month — and you just need the line to paste in. Below are the common cases as copy-ready expressions, grouped by cadence.

Two things to keep in mind as you copy. Most examples use standard five-field crontab syntax — minute, hour, day-of-month, month, day-of-week — and platform-specific variants are labelled. For the why behind the syntax, see How to Read a Cron Expression. A few common requests are exactly where the obvious guess goes wrong: “every 35 minutes,” “every 30 seconds,” “the first Monday,” and “the last day of the month.” For those, this guide gives the fix as well as the line.

Every N minutes

* * * * *      # every minute
*/5 * * * *    # every 5 minutes
*/15 * * * *   # every 15 minutes
*/30 * * * *   # every 30 minutes

*/N in the minute field means “every N minutes,” counting from zero: */15 fires at :00, :15, :30, :45. It behaves as you’d expect only when N divides 60 evenly — 2, 3, 4, 5, 6, 10, 12, 15, 20, 30.

The trap: */35 is not every 35 minutes. A step counts from the start of the field and resets each hour, so */35 fires at :00 and :35, then the hour rolls over and it starts again at :00 — a 35-minute gap followed by a 25-minute one, forever. Any interval that doesn’t divide 60 does this (*/45 gives 45 then 15). For a true 35- or 90-minute cadence, cron’s minute field can’t express it; reach for a systemd timer with a fixed interval or another scheduler.

Every N hours

0 * * * *      # every hour, on the hour
0 */2 * * *    # every 2 hours (00:00, 02:00, 04:00, …)
0 */6 * * *    # every 6 hours (00:00, 06:00, 12:00, 18:00)
15 * * * *     # every hour, at :15 past

Put 0 in the minute field so the job fires once, at the top of the hour, rather than sixty times during it — leaving the minute as * while stepping the hour is a common slip. The same “divides evenly” rule applies to the 24-hour range: */2, */3, */4, */6, */8, */12 are uniform; */5 (0, 5, 10, 15, 20, then a jump back to 0) is not.

Every day at a set time

0 0 * * *      # every day at midnight (00:00)
30 6 * * *     # every day at 06:30
0 9 * * *      # every day at 09:00
0 22 * * *     # every day at 22:00 (10 pm)

Minute first, then hour, both in 24-hour time. 30 6 is 06:30; reverse it to 6 30 and it’s an error, because hour 30 doesn’t exist. Position is the whole meaning here — there are no labels.

Several times a day

0 9,17 * * *     # twice a day, at 09:00 and 17:00
0 0,12 * * *     # twice a day, at 00:00 and 12:00
0 8-18 * * *     # on the hour, 08:00 through 18:00
0 9-17/2 * * *   # every 2 hours from 09:00 to 17:00 (9, 11, 13, 15, 17)

A comma lists specific values; a range with a step (9-17/2) walks the range in increments. The range end is inclusive: 8-18 includes 18:00, and 9-17/2 includes 17:00.

A window that crosses midnight

0 22-23,0-6 * * *   # every hour from 22:00 through 06:00

A range only counts upward, so 22-6 is not a valid overnight window — cron won’t wrap it around midnight, and depending on the parser it either errors or matches nothing. Split it into two ranges that meet at midnight: 22-23 and 0-6. The same goes for any wrap-around: “Friday through Monday” isn’t 5-1 but 5,6,0,1.

Specific days of the week

0 9 * * 1-5       # weekdays (Mon–Fri) at 09:00
0 9 * * 1         # every Monday at 09:00
0 9 * * 0         # every Sunday at 09:00  (0 = Sunday)
0 9 * * 6,0       # weekends (Sat + Sun) at 09:00
0 9 * * MON-FRI   # weekdays, spelled out

The trap: weekday numbers aren’t portable. Standard cron counts 0–7, with 0 and 7 both Sunday. But Cloudflare Workers count 1=Sunday through 7=Saturday, so 1-5 there is Sunday–Thursday, not Monday–Friday; Kubernetes treats 0=Sunday. Spell weekdays with names — MON-FRI, SUN — where your platform accepts them, and the numbering stops mattering. (More on dialect differences in the reading guide.)

Business hours: every N minutes, weekdays only

*/15 9-17 * * 1-5   # every 15 min, 09:00 through 17:xx, Mon–Fri
0 9-17 * * 1-5      # on the hour, 09:00 through 17:00, Mon–Fri

The most common composite schedule, read left to right as: every 15 minutes, during hours 9 through 17, on weekdays. One nuance to expect: the hour range end is inclusive, so 9-17 covers the whole 17:00 hour — the job keeps firing at 17:15, 17:30, 17:45 and stops only at 18:00. If you need it to end at 17:00 sharp, cap the step range at 9-16 and add a final line for the top of 17: 0 17 * * 1-5.

Monthly, and specific days of the month

0 0 1 * *      # 1st of every month at midnight
0 0 15 * *     # the 15th of every month
0 9 1 1,7 *    # Jan 1 and Jul 1 at 09:00 (twice a year)
0 0 1 */3 *    # first of every quarter (Jan, Apr, Jul, Oct)

Three traps live here. First, there’s no plain field for “the last day of the month” — day-of-month is a fixed number and months differ in length. L expresses it (0 0 L * *), but L is a dialect extension (Quartz and a few others), not standard cron, so confirm your platform supports it; the portable fallback is in the next section.

Second, the day-of-month/day-of-week OR rule: if you restrict both the day-of-month and day-of-week fields, standard cron runs when either matches, not both. So 0 0 13 * FRI means “the 13th of every month, and also every Friday” — not “Friday the 13th.” Leave one of the two day fields as * unless you really mean OR.

Third, a day a month doesn’t have simply won’t fire that month. 0 0 31 * * runs only in the seven 31-day months — it skips February, April, June, September, and November — and 0 0 30 2 * (February 30th) never runs at all. For “the end of the month” regardless of length, don’t hard-code 30 or 31; use the guard in the next section.

The Nth weekday of the month

“First Monday,” “last Friday” — common for monthly reports and maintenance windows, and a place the OR rule bites. The tempting 0 9 1-7 * 1 is wrong: with both the day-of-month (1-7) and day-of-week (1) fields set, cron ORs them, so it fires every day from the 1st to the 7th and every Monday. The portable idiom is to schedule the day-of-month window and decide the weekday inside the job:

# First Monday at 09:00 — days 1–7 always contain exactly one Monday
0 9 1-7 * *  [ "$(date +\%u)" = 1 ] && /srv/app/monthly.sh

date +%u is the ISO weekday (1 = Monday … 7 = Sunday); the guard lets the job proceed only on the Monday among days 1–7. Note the % is escaped as \% — an unescaped % in a crontab command is truncation waiting to happen (Why Didn’t My Cron Job Run? covers that trap). For “last Friday,” run every Friday and have the script check whether next Friday falls in a different month. And if your platform is Quartz, the dialect says it directly: MON#1 for the first Monday, 6L for the last Friday.

Sub-minute: “every 30 seconds”

The smallest unit in standard cron is one minute. There’s no seconds field, so “every 30 seconds” can’t be written as a five-field line. There are two ways to approximate it in cron, plus a better fit when reliability matters:

# 1. A six-field cron (Spring, many libraries) puts seconds first:
*/30 * * * * *              # every 30 seconds, where six fields are supported

# 2. Two five-field lines, one offset by a sleep:
* * * * *              /path/to/job
* * * * *  sleep 30 && /path/to/job

The six-field form only works on schedulers that accept a seconds field — count the fields, six means the first one is seconds — and a classic Unix crontab (five fields) will reject it. The sleep 30 pair is a pragmatic hack for a real one-minute crontab, not a precise interval: the two invocations are independent and can overlap when a run lasts longer than 30 seconds. For anything genuinely sub-minute that has to be reliable, a systemd timer with OnUnitActiveSec=30s, or a long-running process with its own loop, fits better than cron.

Named shortcuts

Cronie- and Vixie-style crontabs accept a few named shorthands in place of all five fields:

@hourly    # 0 * * * *
@daily     # 0 0 * * *   (@midnight is the same)
@weekly    # 0 0 * * 0
@monthly   # 0 0 1 * *
@yearly    # 0 0 1 1 *   (@annually is the same)
@reboot    # once, when the daemon starts

They read well, but they’re an extension only some crontabs support — hosted schedulers like GitHub Actions and Cloudflare want the five-field form. @reboot in particular needs a machine with a boot lifecycle, so serverless platforms don’t run it. When in doubt, use the explicit five-field line the shortcut stands for.

When cron’s fields can’t say it

Some calendar schedules simply can’t be written in five fields: the last day of the month, the first Monday, or every weekday except holidays. The usual pattern is to schedule the job more often than you need, then let the job decide whether to continue. Put the condition cron can’t express into a guard at the top of a wrapper script and exit early when it’s not the moment:

0 0 * * *  /srv/app/eom.sh      # run daily…
#!/bin/bash
# eom.sh — proceed only on the last day of the month
[ "$(date -d tomorrow +%d)" = "01" ] || exit 0
# … real work here …

The same pattern handles the rest: run every Friday and exit unless next Friday is in a different month (last Friday); run every weekday and exit on dates in a holiday list. For a fixed elapsed interval such as 90 minutes, don’t force it into an hourly guard — use a fixed-interval scheduler instead. Cron gets the job into the right time window; the script makes the final call. (date -d is GNU coreutils; on BSD or macOS use date -v+1d +%d.)

Verify before you ship

The five-field expressions above are standard cron, but this guide also calls out platform-specific additions such as L, a seconds field, and named shortcuts. Even the same five-field expression can mean different things across platforms — weekday numbers and runtime timezone are two examples. Two habits catch most mistakes:

  • Read it back in a tool. Paste the expression into the cron expression reader: it prints the schedule in plain English and lists the next run times in the timezone you choose, so “every 15 minutes on weekdays” is confirmed rather than assumed. (Expand a @daily-style shortcut to its five-field form first — the reader analyzes numeric fields, not the macros.)
  • Mind the timezone. A cron line carries no timezone of its own; 0 9 * * * is “9:00” in whatever zone the runtime uses — server-local, or UTC on most hosted platforms. If that matters, the reader shows each run in the zone you pick. To compare the server’s offset with yours, use the timezone converter; UTC, GMT, ISO 8601, and Unix time untangles why.

And if a line you’ve verified as correct still doesn’t run, that’s the other half of the story — the environment, the crontab’s location, or the host itself — walked through in Why Didn’t My Cron Job Run?.

Quick reference

You wantThe line
Every minute* * * * *
Every 5 minutes*/5 * * * *
Every 15 minutes*/15 * * * *
Every hour, on the hour0 * * * *
Every 2 hours0 */2 * * *
Every day at midnight0 0 * * *
Every day at 09:000 9 * * *
Twice a day (09:00, 17:00)0 9,17 * * *
Overnight, hourly (22:00–06:00)0 22-23,0-6 * * *
Weekdays at 09:000 9 * * 1-5
Every Monday at 09:000 9 * * 1
Weekends at 09:000 9 * * 6,0
Business hours, every 15 min*/15 9-17 * * 1-5
1st of the month, midnight0 0 1 * *
First of every quarter0 0 1 */3 *
First Monday, 09:000 9 1-7 * * + weekday guard
Last day of monthdaily + date guard