Зачем cron
Запустить команду в N часов каждый день, или каждые 5 минут, или 1-го числа каждого месяца. Сорок лет стандарт Unix. Простой формат, демон всегда есть, не надо ничего писать кроме строки расписания.
На современных Linux его всё чаще вытесняет systemd-timers - но cron всё ещё установлен в RHEL/Debian/Alpine из коробки и продолжает обслуживать миллионы серверов.
Crontab - где лежит расписание
Три уровня:
| Файл | Кто пишет | Поле user |
|---|---|---|
crontab -e (юзера) | пользователь, через crontab | нет |
/etc/crontab | root | есть (6-е поле) |
/etc/cron.d/* | пакеты, root | есть |
/etc/cron.{hourly,daily,weekly,monthly}/ | скрипты | run-parts |
Личный crontab пользователя:
crontab -e # редактировать
crontab -l # показать
crontab -r # удалить весь (внимание!)
sudo crontab -u www-data -l # crontab другого юзера
Системные /etc/cron.d/ отличаются 6-полевым форматом - перед командой
идёт имя пользователя:
# m h dom mon dow user command
0 3 * * * root /usr/local/bin/backup.sh
Формат строки
┌─── минута (0-59)
│ ┌─── час (0-23)
│ │ ┌─── день месяца (1-31)
│ │ │ ┌─── месяц (1-12 или JAN-DEC)
│ │ │ │ ┌─── день недели (0-7, 0 и 7 = Sunday, или MON-SUN)
│ │ │ │ │
* * * * * command
Спецсимволы:
| Символ | Смысл |
|---|---|
* | каждое значение |
, | список: 1,15,30 |
- | диапазон: 9-17 |
*/N | каждые N: */5 каждые 5 |
0-23/2 | диапазон с шагом |
Примеры:
*/5 * * * * ./tick.sh # каждые 5 минут
0 3 * * * ./backup.sh # каждый день в 03:00
0 2 * * 0 ./weekly.sh # каждое воскресенье в 02:00
0 0 1 * * ./monthly.sh # 1-го числа в полночь
30 9-18 * * 1-5 ./reminder.sh # каждые час и 30 минут с 09:30 до 18:30, пн-пт
@-сокращения
| Алиас | Эквивалент |
|---|---|
@reboot | при старте cron'а (≈ при загрузке системы) |
@yearly, @annually | 0 0 1 1 * |
@monthly | 0 0 1 * * |
@weekly | 0 0 * * 0 |
@daily, @midnight | 0 0 * * * |
@hourly | 0 * * * * |
@reboot - удобно для одноразовой команды при загрузке без
systemd-unit, но надёжнее использовать systemd-targets.
Anacron - для выключаемых машин
cron не запустит то, что должно было выполниться пока машина была выключена. Anacron нагоняет: при загрузке проверяет, когда задача выполнялась последний раз и если "пора" - выполнит сейчас.
/etc/anacrontab:
# period delay job-id command
1 5 cron.daily run-parts /etc/cron.daily
7 25 cron.weekly run-parts /etc/cron.weekly
period- через сколько днейdelay- случайная задержка после старта (минут)
На laptop-системах, где cron-@daily пропустится при сне, anacron
его выполняет при пробуждении.
Окружение cron'а - источник 80% багов
cron запускает команду в минимальном окружении: PATH=/usr/bin:/bin,
HOME=$HOME юзера, никаких переменных из ~/.bashrc или
/etc/profile. Скрипт работающий из shell часто падает в cron из-за этого.
Лекарство:
# В начале crontab задать переменные
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=ops@example.com
TZ=Europe/Berlin
0 3 * * * /home/user/backup.sh
PATH- часто причина "command not found"MAILTO- cron шлёт stdout/stderr на этот адрес (если установлен MTA)SHELL- по дефолту/bin/sh; для bash-onlineisms укажи bashTZ- cron берёт системную; на UTC-сервере "0 3" это 3 ночи UTC
Логи
journalctl -u cron -f # systemd-системы (Debian)
journalctl -u crond -f # RHEL
grep CRON /var/log/syslog # старая Debian
/var/log/cron # RHEL
Cron логирует факт запуска, не stdout/stderr. Чтобы увидеть вывод -
либо MAILTO, либо в самой команде:
0 3 * * * /home/user/backup.sh >> /var/log/backup.log 2>&1
Без 2>&1 ошибки уйдут в почту, а не в лог. Лучшая практика -
всё в один файл с timestamp'ом:
0 3 * * * /home/user/backup.sh 2>&1 | logger -t backup
Тогда вывод попадает в journalctl -t backup со штампами.
cron vs systemd-timers
| Признак | cron | systemd-timers |
|---|---|---|
| Формат | строка | .timer + .service unit |
| Логи | в syslog как факт | полный stdout/stderr через journald |
| Завиcимости | нет | After/Requires |
| Случайная задержка | anacron | RandomizedDelaySec= |
| Persistence | anacron | Persistent=true |
| Пропущенные при сне | anacron | OnBootSec= |
| Тяжёлый процесс | живёт без надзора | resource-limits, cgroups |
Для нового кода на systemd-системах timers честнее.
Когда что-то пошло не так
command not found- PATH не содержит/usr/local/bin. Задай в crontab.- Скрипт не нашёл файл - cron работает в
$HOMEюзера; либо абсолютные пути, либоcdпервой строкой скрипта. - Не запустился ВООБЩЕ - проверь
systemctl status cron,crontab -l(синтаксис), правильные права на скрипт (chmod +x). - Запустился, но молча упал - нет
MAILTO, нет редиректа stdout/stderr. Перенаправь вывод в файл и читай. - Запускается дважды - в
/etc/crontabи в personal crontab одно и то же. - Перевод времени, DST - в моменты перехода часов задание может выполниться 0 или 2 раза. Anacron-style + idempotency спасает.
%в crontab команде - спецсимвол; экранируй\%или закрой команду в/bin/bash -c '...'.