Пять полей, шесть спецсимволов и пара ловушек, на которых спотыкаются все. Ниже — таблицы, готовые выражения и то же самое в формате systemd.
┌───────────── минута (0–59) │ ┌─────────── час (0–23) │ │ ┌───────── день месяца (1–31) │ │ │ ┌─────── месяц (1–12 или JAN–DEC) │ │ │ │ ┌───── день недели (0–7 или SUN–SAT, 0 и 7 — воскресенье) │ │ │ │ │ * * * * * команда
| Поле | Диапазон | Примечание |
|---|---|---|
| Минута | 0–59 | — |
| Час | 0–23 | 0 — полночь |
| День месяца | 1–31 | 31-е бывает не каждый месяц |
| Месяц | 1–12, JAN–DEC | регистр не важен |
| День недели | 0–7, SUN–SAT | и 0, и 7 — воскресенье |
| Символ | Значение | Пример |
|---|---|---|
* | любое значение поля | * * * * * — каждую минуту |
, | перечисление | 0 9,18 * * * — в 9:00 и 18:00 |
- | диапазон | 0 9-18 * * * — каждый час с 9 до 18 |
/ | шаг внутри диапазона | */15 * * * * — каждые 15 минут |
L | последний день (не во всех cron) | 0 3 L * * — в последний день месяца |
W | ближайший будний день (не во всех cron) | 0 3 15W * * — рабочий день рядом с 15-м |
# | n-й день недели месяца (не во всех cron) | 0 3 * * 1#2 — второй понедельник |
L, W и
# — расширения Quartz. Классический
Vixie cron из Debian и Ubuntu их не понимает; CronAlive
принимает стандартные пять полей и сокращения ниже.
| Сокращение | Эквивалент |
|---|---|
@yearly, @annually | 0 0 1 1 * |
@monthly | 0 0 1 * * |
@weekly | 0 0 * * 0 |
@daily, @midnight | 0 0 * * * |
@hourly | 0 * * * * |
@reboot | один раз при старте системы |
| Когда | Выражение |
|---|---|
| каждую минуту | * * * * * |
| каждые 5 минут | */5 * * * * |
| каждые 30 минут | 0,30 * * * * |
| каждый час в :15 | 15 * * * * |
| каждые 2 часа | 0 */2 * * * |
| ежедневно в 3:30 | 30 3 * * * |
| дважды в день, 6:00 и 18:00 | 0 6,18 * * * |
| по будням в 9:00 | 0 9 * * 1-5 |
| по выходным в 10:00 | 0 10 * * 6,0 |
| каждый понедельник в 4:00 | 0 4 * * 1 |
| 1-го числа в 0:00 | 0 0 1 * * |
| каждые 15 минут в рабочие часы | */15 9-18 * * 1-5 |
*/60 * * * * — не «раз в
час». Шаг применяется внутри диапазона поля: минуты — это
0–59, и единственное подходящее значение — 0. Выражение
запустится в начале каждого часа, но по случайности, а не по
смыслу. Правильно: 0 * * * *.
Так же */70 не сработает вовсе.
Когда оба поля заданы (не *),
cron объединяет их по ИЛИ, а не по И. Выражение
0 3 13 * 5 сработает и 13-го
числа любого месяца, и каждую пятницу — а не только
в пятницу 13-го. Это поведение исторически заложено в Vixie
cron; чтобы получить «И», приходится проверять дату внутри
скрипта.
Оба значения валидны, а вот 1-7
означает «понедельник–воскресенье», то есть всю неделю, а не
«будни». Будни — 1-5.
Cron работает в таймзоне системы (или в
CRON_TZ, если он задан в начале
crontab). Сервер в UTC, а расписание вы читаете как
московское — минус три часа к ожиданиям. В чеке CronAlive
таймзона задаётся явно, включая переходы на летнее время.
В crontab % — это перевод
строки, а не символ. Строка с
date +%Y-%m-%d оборвётся на
первом же проценте. Экранируйте:
\%.
У cron минимальный PATH и нет
вашего профиля: ~/.bashrc не
читается. Пишите абсолютные пути — и к своей задаче, и к
curl.
Таймеры systemd постепенно вытесняют cron. Формат другой:
день-недели ГГГГ-ММ-ДД ЧЧ:ММ:СС,
где * — любое значение, а
/ — шаг.
| Когда | cron | OnCalendar |
|---|---|---|
| каждую минуту | * * * * * | *-*-* *:*:00 |
| каждые 5 минут | */5 * * * * | *:0/5 |
| каждый час | 0 * * * * | hourly |
| ежедневно в 3:30 | 30 3 * * * | *-*-* 03:30:00 |
| по будням в 9:00 | 0 9 * * 1-5 | Mon..Fri 09:00 |
| 1-го числа в 0:00 | 0 0 1 * * | monthly |
Проверить выражение и увидеть ближайшие запуски:
systemd-analyze calendar 'Mon..Fri 09:00' --iterations=3
У таймеров есть то, чего нет у cron:
Persistent=true навёрстывает
пропущенный запуск после выключения машины, а
RandomizedDelaySec= разносит
одновременные задачи. Пример юнита с пингами — в
сниппетах.
Корректное выражение ничего не гарантирует: cron мог не перечитать crontab, задача — упасть на первой строке, а сервер — уйти в ребут. Создайте чек с тем же расписанием, и вы узнаете о молчании в тот же час: мониторинг cron-джобов за 5 минут.