Five fields, six special characters and a couple of traps everybody falls into. Tables, ready-made expressions and the same thing in systemd format below.
┌───────────── minute (0–59) │ ┌─────────── hour (0–23) │ │ ┌───────── day of month (1–31) │ │ │ ┌─────── month (1–12 or JAN–DEC) │ │ │ │ ┌───── day of week (0–7 or SUN–SAT, 0 and 7 are Sunday) │ │ │ │ │ * * * * * command
| Field | Range | Note |
|---|---|---|
| Minute | 0–59 | — |
| Hour | 0–23 | 0 is midnight |
| Day of month | 1–31 | the 31st does not exist every month |
| Month | 1–12, JAN–DEC | case does not matter |
| Day of week | 0–7, SUN–SAT | both 0 and 7 mean Sunday |
| Character | Meaning | Example |
|---|---|---|
* | any value of the field | * * * * * — every minute |
, | a list | 0 9,18 * * * — at 9:00 and 18:00 |
- | a range | 0 9-18 * * * — hourly from 9 to 18 |
/ | a step inside the range | */15 * * * * — every 15 minutes |
L | last day (not in every cron) | 0 3 L * * — on the last day of the month |
W | nearest weekday (not in every cron) | 0 3 15W * * — the workday nearest the 15th |
# | nth weekday of the month (not in every cron) | 0 3 * * 1#2 — the second Monday |
L, W
and # are Quartz extensions. The
classic Vixie cron shipped with Debian and Ubuntu does not
understand them; CronAlive accepts the standard five fields
and the shorthands below.
| Shorthand | Equivalent |
|---|---|
@yearly, @annually | 0 0 1 1 * |
@monthly | 0 0 1 * * |
@weekly | 0 0 * * 0 |
@daily, @midnight | 0 0 * * * |
@hourly | 0 * * * * |
@reboot | once when the system starts |
| When | Expression |
|---|---|
| every minute | * * * * * |
| every 5 minutes | */5 * * * * |
| every 30 minutes | 0,30 * * * * |
| hourly at :15 | 15 * * * * |
| every 2 hours | 0 */2 * * * |
| daily at 3:30 | 30 3 * * * |
| twice a day, 6:00 and 18:00 | 0 6,18 * * * |
| weekdays at 9:00 | 0 9 * * 1-5 |
| weekends at 10:00 | 0 10 * * 6,0 |
| every Monday at 4:00 | 0 4 * * 1 |
| the 1st of the month at 0:00 | 0 0 1 * * |
| every 15 minutes during office hours | */15 9-18 * * 1-5 |
*/60 * * * * does not mean
"once an hour". The step applies inside the field's range:
minutes are 0–59, so the only matching value is 0. It will
fire at the top of every hour — by accident, not by meaning.
The correct form is 0 * * * *.
Likewise */70 never fires at all.
When both fields are set (neither is
*), cron combines them with
OR, not AND. 0 3 13 * 5
fires on the 13th of every month and on every Friday
— not only on Friday the 13th. This behaviour has been baked
into Vixie cron from the start; to get AND you have to check
the date inside the script.
Both values are valid, but
1-7 means Monday through Sunday,
that is the whole week — not "weekdays". Weekdays are
1-5.
Cron runs in the system time zone (or in
CRON_TZ if it is set at the top
of the crontab). A server on UTC and a schedule you read as
local time differ by exactly the offset you forgot. In a
CronAlive check the time zone is explicit, daylight saving
transitions included.
In a crontab % is a newline, not
a character. A line with
date +%Y-%m-%d is cut at the
first percent. Escape it:
\%.
Cron has a minimal PATH and none
of your profile: ~/.bashrc is not
read. Use absolute paths — both for your job and for
curl.
systemd timers are slowly replacing cron. The format is
different:
day-of-week YYYY-MM-DD HH:MM:SS,
where * is any value and
/ is a step.
| When | cron | OnCalendar |
|---|---|---|
| every minute | * * * * * | *-*-* *:*:00 |
| every 5 minutes | */5 * * * * | *:0/5 |
| hourly | 0 * * * * | hourly |
| daily at 3:30 | 30 3 * * * | *-*-* 03:30:00 |
| weekdays at 9:00 | 0 9 * * 1-5 | Mon..Fri 09:00 |
| the 1st at 0:00 | 0 0 1 * * | monthly |
Check an expression and see the next runs:
systemd-analyze calendar 'Mon..Fri 09:00' --iterations=3
Timers have something cron does not:
Persistent=true catches up a run
missed while the machine was off, and
RandomizedDelaySec= spreads
simultaneous jobs apart. A unit with pings is in the
snippets.
A correct expression guarantees nothing: cron may not have reloaded the crontab, the job may die on its first line, the server may reboot. Create a check with the same schedule and you will hear about the silence the same hour: cron job monitoring in 5 minutes.