Парсер выражений cron
Прочитайте выражение cron обычным языком и узнайте, когда именно оно сработает в следующий раз.
минута · час · день месяца · месяц · день недели. Макросы вроде @daily тоже работают.
Запускается
в 03:00, по понедельникам
Ближайшие шесть запусков · UTC
- 2026-09-21 03:00понедельник
- 2026-09-28 03:00понедельник
- 2026-10-05 03:00понедельник
- 2026-10-12 03:00понедельник
- 2026-10-19 03:00понедельник
- 2026-10-26 03:00понедельник
Показано в UTC. Настоящий crontab работает в часовом поясе сервера — именно там переход на летнее время и создаёт проблемы, см. ниже.
Поле за полем
- минута
- 00
- час
- 33
- день месяца
- *любое
- месяц
- *любое
- день недели
- 11
Что стоит знать об этом расписании
- 31-е число есть не в каждом месяце, поэтому в таких месяцах задание просто не запустится.
- 29 февраля бывает только в високосные годы — значит, запуск раз в четыре года.
Пять полей, самое грубое — последнее
| Позиция | Поле | Диапазон | Примечания |
|---|---|---|---|
| 1 | Минута | 0–59 | |
| 2 | Час | 0–23 | 24-часовой формат, время сервера |
| 3 | День месяца | 1–31 | Несуществующие дни просто пропускаются |
| 4 | Месяц | 1–12 | Или JAN–DEC |
| 5 | День недели | 0–7 | И 0, и 7 означают воскресенье. Или SUN–SAT |
Внутри поля: * — любое значение, , перечисляет, - задаёт диапазон, / — шаг. */15 в поле минут — это каждые пятнадцать минут; 5/10 означает «от 5 и далее через десять», то есть 5, 15, 25 и так далее. Диапазоны могут заворачиваться, поэтому FRI-MON — корректный способ сказать «выходные и их края».
Правило, на котором спотыкаются все
Вот единственное, что стоит унести с этой страницы.
Когда ограничены оба поля — день месяца и день недели, — cron запускает задачу, если совпадает любое из них. Не оба. Это «или», а читается как «и».
0 0 1 * MON # NOT "the 1st, if it is a Monday"
# Actually: every 1st, AND every MondayТакое расписание срабатывает примерно пять раз в месяц. Вставьте его в разборщик выше и посмотрите на ближайшие запуски — последовательность выдаёт это сразу.
Правило действует только тогда, когда ограничены оба поля. Если хотя бы одно равно *, всё определяет другое, поэтому 0 0 * * MON ведёт себя ровно так, как ожидается. Поведение прописано в спецификации POSIX, и его реализует любой распространённый cron, так что это не ошибка, которую можно отключить настройкой.
Чтобы действительно требовать оба условия, ограничьте одно поле в расписании, а второе проверьте в начале самой задачи:
0 0 * * MON [ "$(date +\%d)" = "01" ] && /usr/local/bin/jobПереход на летнее время, который ломает задачи дважды в год
Если не сказано иное, cron работает в местном часовом поясе сервера, а местное время не непрерывно. При переводе часов вперёд час исчезает: задача на 02:30 может быть пропущена целиком или запущена в 03:30 — смотря какой у вас cron. При переводе назад 01:30 наступает дважды, и задача может отработать столько же.
Современный Vixie cron старается — пропущенные из-за скачка вперёд задачи обычно выполняются один раз позже, — но поведение неодинаково в разных реализациях, а планировщики контейнеров добавляют ещё разнобоя.
Долговечное решение — держать планировщик в UTC и преобразовывать на границах. CronJob в Kubernetes по умолчанию работает в UTC именно поэтому. Если задача обязана выполняться в конкретное местное время, смиритесь, что часть года она будет уходить на час, — или возьмите планировщик, который по-настоящему понимает часовые пояса: таймеры systemd понимают.
Другие причины, по которым задача cron не выполняется
- Окружение почти пусто. Cron не подгружает профиль вашей оболочки, поэтому
PATHминимален, а виртуальное окружение не активировано. Всегда используйте абсолютные пути. - Голый знак процента. В crontab
%означает перевод строки.date +%Yтихо ломается; писать надоdate +\%Y. - Нет перевода строки в конце. Некоторые cron игнорируют последнюю строку crontab, если она не оканчивается им.
- Вывод уходит в никуда. Cron отправляет вывод письмом пользователю, и если почта не настроена, он пропадает. Перенаправьте в файл журнала, чтобы отказы были видны.
- Наложение запусков. Cron не ждёт завершения предыдущего запуска. Пятиминутная задача в ежеминутном расписании накапливается, пока что-нибудь не рухнет. Используйте
flock.
Чего cron выразить не может
Поля независимы, поэтому всё, что не делится нацело на час или сутки, недостижимо. «Каждые 90 минут» записать нельзя. Как и «последнюю пятницу месяца» в стандартном cron — для этого и существуют L и # в Quartz, и потому же они непереносимы.
Если вы ловите себя на борьбе с синтаксисом, это обычно сигнал перейти к планировщику, который мыслит интервалами, а не календарными полями. Таймер systemd с OnUnitActiveSec=90min говорит то, что вы имели в виду, одной строкой.
Смежное
Времена запусков выше указаны в UTC — конвертер меток времени переведёт любое из них в ваш часовой пояс или в тот формат, которым пишет журналы ваш планировщик. Если вам нужен поиск по тексту, а не расписание, тестер регулярных выражений — по соседству.
Вопросы о cron
Почему моя задача запускается в дни, которые я не назначал?
Почти наверняка из-за правила «день месяца и день недели». Когда оба поля ограничены, cron запускает задачу, если совпадает ЛЮБОЕ из них, а не оба сразу. Поэтому 0 0 1 * MON срабатывает и первого числа каждого месяца, и каждый понедельник — то есть примерно пять раз в месяц вместо одного задуманного. Если хотя бы одно поле равно *, правило не применяется и всё определяет второе. Чтобы требовать оба условия, ограничьте одно поле в cron, а второе проверяйте внутри самой задачи.
Что означают пять полей?
Минута (0–59), час (0–23), день месяца (1–31), месяц (1–12), день недели (0–7, где и 0, и 7 — воскресенье). Слева направо шаг становится всё крупнее — полезный мнемонический приём. Некоторые планировщики — Quartz, Spring и ряд исполнителей задач — добавляют впереди шестое поле для секунд; в стандартном crontab поля секунд нет вовсе.
Что происходит с задачей cron при переходе на летнее время?
Зависит от реализации, и именно здесь запланированные задачи тихо ломаются дважды в год. При переводе часов вперёд одного часа просто не существует: задача на 02:30 может быть пропущена целиком или выполниться в 03:30 — как решит конкретный cron. При переводе назад 01:30 наступает дважды, и задача может отработать дважды. Современные Vixie cron и таймеры systemd стараются вести себя разумно, старые — нет. Надёжный ответ: выполнять запланированную работу в UTC, а преобразовывать только на границах.
Что такое L, W и # и почему они здесь отвергаются?
Это расширения Quartz: L — «последний» (последний день месяца, последняя пятница), W — ближайший рабочий день, # — «n-й такой-то день недели в месяце». Они по-настоящему удобны и при этом не являются стандартным cron. Ни crontab, ни CronJob в Kubernetes, ни GitHub Actions их не примут или поймут неправильно. Этот разборщик отказывает, а не гадает, потому что расписание, молча проглотившее L, ошибётся ровно тем способом, который никто не проверяет.
Что делает @reboot?
Запускает задачу один раз при старте cron, обычно при загрузке системы. Периодического расписания у неё нет, поэтому и вычислять нечего — потому инструмент так и пишет, вместо того чтобы показывать пустой список. Полезно знать, что @reboot срабатывает при запуске демона cron, а не когда машина закончила загружаться, так что всё, что зависит от поднявшейся сети, должно ждать её самостоятельно.
Как запускать что-то каждые 90 минут?
Напрямую это не выражается, и это настоящее ограничение, а не приём, которого вы не выучили. Поля cron независимы, поэтому */90 в поле минут недопустимо, и составить расписание, не делящее нацело час или сутки, невозможно. Обходные пути — две записи, покрывающие нужный рисунок в пределах суток, или, если честнее, планировщик, мыслящий интервалами, а не календарными полями: например, таймер systemd с OnUnitActiveSec.
Последняя проверка . Заметили, что что-то устарело? Напишите нам.
