Анатомия 5 полей классического cron
Классический планировщик Unix (Vixie cron, cronie) принимает расписание ровно из пяти полей, разделённых пробелами или символами табуляции. Каждое поле отвечает за конкретную единицу времени в строгой последовательности:
* * * * * команда для выполнения
│ │ │ │ │
│ │ │ │ └─── День недели (0 - 7, где 0 и 7 — воскресенье, или SUN-SAT)
│ │ │ └───── Номер месяца (1 - 12 или JAN-DEC)
│ │ └─────── Число месяца (1 - 31)
│ └───────── Час суток (0 - 23 в формате 24h)
└─────────── Минута часа (0 - 59) | Поле | Единица времени | Допустимый диапазон | Разрешенные спецсимволы |
|---|---|---|---|
| 1 | Минуты | 0–59 | * , - / |
| 2 | Часы | 0–23 | * , - / |
| 3 | День месяца | 1–31 | * , - / |
| 4 | Месяц | 1–12 или JAN–DEC | * , - / |
| 5 | День недели | 0–7 (0 и 7 = Вс) или SUN–SAT | * , - / |
Обратите внимание: в корпоративных фреймворках (Java Spring Scheduler, Quartz Scheduler, AWS CloudWatch Events) часто используется нестандартный 6- или 7-полевой формат, где первым полем идут секунды (0-59), а последним — год. Попытка вставить 6 полей в стандартный Linux crontab -e приведёт к тому, что шестое поле посчитается частью исполняемой команды.
Специальные символы и операторы
Гибкость планирования обеспечивается четырьмя базовыми операторами:
- Звёздочка (
*) — любое допустимое значение поля. Строка* * * * *означает «каждую минуту каждого часа каждого дня». - Запятая (
,) — перечисление дискретных значений. Например,15,45 * * * *выполняет задачу в 15 и 45 минут каждого часа. - Дефис (
-) — непрерывный диапазон. Например,0 9-18 * * 1-5запускает процесс в начале каждого рабочего часа (с 9:00 до 18:00) с понедельника по пятницу. - Косая черта (
/) — шаг инкремента в пределах диапазона. Например,*/10 * * * *означает «каждые 10 минут начиная с нулевой» (0, 10, 20, 30, 40, 50). А5-55/15 * * * *выполнит запуск в 5, 20, 35 и 50 минут.
Коварная логика «ИЛИ» между днем месяца и днем недели
Самая частая логическая ловушка для сисадминов и бэкендеров: если в расписании одновременно заданы поле 3 (число месяца) и поле 5 (день недели), и ни одно из них не является чистой звёздочкой, демон cron вычисляет их объединение по логическому ИЛИ (OR), а не логическому И (AND)!
Рассмотрим строку: 0 2 13 * 5 /opt/backup.sh. Интуитивно ожидается: «запустить в 02:00 в пятницу 13-го числа». На практике cron запустит скрипт 13-го числа каждого месяца И каждую пятницу недели!
Чтобы задача гарантированно выполнялась только при одновременном совпадении обоих условий (пятница тринадцатое), проверку дня недели выносят в команду шелла:
0 2 13 * * [ $(date +\%u) -eq 5 ] && /opt/scripts/friday13_backup.sh Специальные макросы (@reboot, @daily, @hourly)
Для типовых интервалов стандарт Vixie cron поддерживает удобные короткие строковые псевдонимы (макросы), заменяющие пять полей времени:
| Макрос | Эквивалент | Периодичность выполнения |
|---|---|---|
@reboot | — | Один раз при инициализации демона cron после загрузки ОС |
@yearly / @annually | 0 0 1 1 * | 1 января в 00:00 (раз в год) |
@monthly | 0 0 1 * * | В первый день каждого месяца в 00:00 |
@weekly | 0 0 * * 0 | Каждое воскресенье в полночь (00:00) |
@daily / @midnight | 0 0 * * * | Каждый день ровно в полночь (00:00) |
@hourly | 0 * * * * | В начале каждого часа (минута 0) |
Псевдоним @reboot особенно удобен для автоматического подъёма микросервисов, docker-compose связок или фоновых очередей без написания отдельного systemd unit файла:
@reboot sleep 15 && cd /opt/apps/myproject && docker compose up -d Часовые пояса и коварство перехода на летнее время (DST)
По умолчанию демон cron ориентируется на системные системные часы и файл /etc/localtime. В современных версиях cron (Debian, Ubuntu, RHEL) в самом начале файла crontab можно явно объявить целевую зону:
CRON_TZ=UTC
# Либо конкретный региональный пояс:
# CRON_TZ=Europe/Moscow
0 3 * * * /opt/scripts/nightly_report.sh Если сервер настроен на региональный часовой пояс со сменой сезонного времени (Daylight Saving Time, переход на летнее/зимнее время в США и странах Европы):
- Весной при переводе часов на 1 час вперёд (с 02:00 на 03:00) окно с 02:00 до 02:59 физически исчезает. Задачи, запланированные на этот час, не выполнятся вовсе.
- Осенью при переводе часов назад (с 03:00 на 02:00) часовое окно повторяется дважды. Задачи, назначенные на этот период, будут запущены дважды.
Именно поэтому в производственной инфраструктуре незыблемым правилом является синхронизация всех серверов и баз данных со всемирным координированным временем (UTC): timedatectl set-timezone UTC.
7 главных причин, почему crontab не работает
Когда скрипт безупречно отрабатывает при ручном запуске в терминале, но наотрез отказывается выполняться через crontab, виной почти всегда является одно из семи фундаментальных различий между интерактивной сессией и окружением cron:
- Урезанная переменная PATH. При интерактивном входе в систему шелл подгружает длинный путь из
/etc/environment,~/.bashrcи~/.profile. Демон cron запускает задачи с аскетичным значением по умолчанию:PATH=/usr/bin:/bin. Если ваш скрипт вызываетdocker,node,python3из/usr/local/bin,/snap/binили папки nvm/pyenv, он завершится с ошибкой command not found. Всегда объявляйтеPATHв начале crontab или используйте абсолютные пути к исполняемым файлам. - Текущая рабочая директория (CWD). Демон cron всегда стартует задачу в домашней папке пользователя (
$HOME), а не в каталоге со скриптом. Любые вызовы с относительными путями (python script.py, чтение./config.jsonили запись вlogs/app.log) немедленно сломаются. Внутри crontab всегда делайте явный переход:cd /opt/app && ./run.sh. - Неэкранированный знак процента (%). В синтаксисе crontab знак
%имеет специальное значение: он заменяется на символ новой строки, а вся последующая часть строки передаётся запущенной команде на стандартный ввод (stdin). Попытка добавить дату в имя архива вродеtar -czf backup-$(date +%Y-%m-%d).tar.gzфатально разорвёт строку. Все знаки процента внутри crontab обязаны экранироваться обратным слешем:date +\%Y-\\%m-\\%d. - Интерпретатор /bin/sh вместо /bin/bash. По умолчанию задачи исполняются через системный
/bin/sh(в Ubuntu это лёгкий dash). Попытки использовать специфичные для bash конструкции (двойные квадратные скобки[[ ... ]], массивы, замену строк) вызовут синтаксическую ошибку syntax error: unexpected token. Задайте шелл явно:SHELL=/bin/bash. - Отсутствие прав на исполнение (chmod +x). Файл сценария должен иметь бит исполняемости для владельца задачи (
chmod +x /path/to/script.sh) и корректный шебанг (например,#!/usr/bin/env bashили#!/usr/bin/env python3) в самой первой строке. - Отсутствие пустой строки в конце файла. Классический Vixie cron парсит файл построчно, ориентируясь на символ
\n. Если последняя строка crontab не заканчивается переводом строки, демон тихо проигнорирует её без генерации каких-либо ошибок. Всегда оставляйте пустую строку в конце файла при редактировании. - Отсутствие переменных окружения (.env). Интерактивные переменные (токены доступа, пароли к базам данных,
NODE_ENV=production) не передаются в задачу. Переменные необходимо либо объявлять прямо в crontab перед командами, либо явно подключать файл конфигурации:set -a; . /opt/app/.env; set +a; /opt/app/worker.
Логирование, отладка и защита от дубликатов (flock)
По умолчанию cron пытается отправить весь вывод упавших задач локальной почтой (через sendmail/postfix). Если почтовый агент на сервере не настроен, ошибки бесследно исчезают в пустоте.
Перенаправление вывода в лог-файл
Чтобы никогда не терять сообщения об ошибках, всегда перенаправляйте стандартный вывод (stdout) и стандартный поток ошибок (stderr) в целевой лог:
# Дописывать вывод в файл (режим append)
0 4 * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>&1
# Отключить вывод полностью, если скрипт пишет собственные логи:
0 * * * * /opt/scripts/sync.sh > /dev/null 2>&1 Как посмотреть системные логи cron
Если вы сомневаетесь, инициировал ли демон запуск задачи вообще, проверьте системный журнал операционной системы:
# В дистрибутивах с systemd (Ubuntu, Debian, CentOS, RHEL):
journalctl -u cron.service -e --no-pager
# Либо через классический системный лог:
grep CRON /var/log/syslog | tail -n 50 Идеальная эмуляция окружения для отладки
Чтобы не сидеть часами в ожидании времени срабатывания задачи, протестируйте скрипт в спартанском окружении cron одной командой:
env -i HOME="$HOME" PATH="/usr/bin:/bin" SHELL="/bin/sh" /path/to/my_script.sh Защита от повторного запуска с помощью flock
Если минутная задача внезапно подвисла на выполнении тяжелого сетевого запроса или база данных обрабатывает медленный запрос дольше обычного, следующая итерация запустится параллельно. При накоплении десятков таких процессов сервер исчерпает стек дескрипторов и память.
Для предотвращения параллельного исполнения используйте утилиту блокировки flock:
# Флаг -n (non-blocking) немедленно завершает новую задачу, если лок-файл уже занят:
* * * * * flock -n /var/lock/worker_sync.lock /usr/bin/python3 /opt/app/sync.py >> /var/log/sync.log 2>&1 Частые вопросы
Почему в cron для дня недели разрешены и 0, и 7?
В стандарте Vixie cron и стандарте POSIX оба значения (0 и 7) обозначают воскресенье. Это сделано для совместимости с различными региональными стандартами: в одних системах отсчёт недели традиционно начинается с воскресенья (0 = Sun), в других воскресенье считается седьмым днём (7 = Sun).
Как проверить команду из crontab, не дожидаясь времени запуска?
Запустите команду вручную от имени того же пользователя с изолированным чистым окружением: env -i HOME="$HOME" PATH="/usr/bin:/bin" SHELL="/bin/sh" /path/to/script.sh. Это в точности воспроизведёт спартанское окружение демона cron и мгновенно выявит ненайденные пути или переменные.
Чем отличается crontab пользователя от системного /etc/crontab?
Пользовательский crontab (crontab -e) состоит из 5 полей времени и команды. Системные файлы (/etc/crontab и файлы в каталоге /etc/cron.d/) содержат обязательное 6-е поле — имя пользователя, от которого запускается процесс (например: * * * * * root /usr/local/bin/backup.sh).
Что произойдет, если предыдущий запуск cron-задачи еще не завершился?
По умолчанию cron запустит новую копию задачи параллельно. Для тяжелых скриптов это приводит к лавинообразному исчерпанию оперативной памяти и процессора. Для предотвращения наложений оборачивайте вызовы в утилиту flock: flock -n /var/lock/myjob.lock /path/to/script.sh.
Почему конструкция date +%Y-%m-%d ломает строку в crontab?
В синтаксисе cron символ процента (%) считается специальным знаком перевода строки (newline), а текст после него передается команде через стандартный ввод (stdin). Чтобы знак процента передался в саму утилиту date, его необходимо экранировать обратным слешем: date +\%Y-\%m-\%d.