# cron и crontab - расписание задач _Команды · LinuxLab Knowledge Base_ **TL;DR:** cron - демон, который читает crontab-файлы и запускает задачи по расписанию. Формат: `min hour day month weekday command`. Anacron для выключаемых машин. На systemd-системах часто заменяется timers. ## Зачем cron Запустить команду в N часов каждый день, или каждые 5 минут, или 1-го числа каждого месяца. Сорок лет стандарт Unix. Простой формат, демон всегда есть, не надо ничего писать кроме строки расписания. На современных Linux его всё чаще вытесняет [systemd-timers](/kb/systemd-timers.md) - но 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 пользователя: ```bash 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](/kb/systemd-targets.md). ## 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 из-за этого. Лекарство: ```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 укажи bash - **`TZ`** - cron берёт системную; на UTC-сервере "0 3" это 3 ночи UTC ## Логи ```bash 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`, либо в самой команде: ```cron 0 3 * * * /home/user/backup.sh >> /var/log/backup.log 2>&1 ``` Без `2>&1` ошибки уйдут в почту, а не в лог. Лучшая практика - всё в один файл с timestamp'ом: ```cron 0 3 * * * /home/user/backup.sh 2>&1 | logger -t backup ``` Тогда вывод попадает в `journalctl -t backup` со штампами. ## cron vs systemd-timers | Признак | cron | [systemd-timers](/kb/systemd-timers.md) | |---------|------|--------------------| | Формат | строка | `.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 '...'`. ## Команды ```bash crontab -e ``` Редактировать crontab текущего юзера в `$EDITOR` ```bash crontab -l ``` Показать активный crontab - первое что смотреть при отладке ```bash sudo crontab -u www-data -l ``` Crontab другого юзера - нужен sudo ```bash * * * * * date >> /tmp/cron-test.log ``` Каждую минуту в файл - дебаг что cron в принципе работает ```bash 0 3 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1 ``` Каждый день в 03:00, и stdout, и stderr в один лог ```bash @reboot /home/user/start-tunnel.sh ``` Запустить при загрузке - быстрая замена systemd-юниту ```bash journalctl -u cron --since '1 hour ago' ``` Логи cron'а через journalctl - видны попытки запуска ## См. также - [systemd timers - замена cron](/kb/systemd-timers.md) - [systemctl - управление сервисами systemd](/kb/cmd-systemctl.md) - [journalctl - журнал systemd](/kb/cmd-journalctl.md) - [bash-скрипты - основы и идиомы](/kb/bash-scripting.md) - [Сигналы (SIGTERM, SIGKILL, SIGHUP)](/kb/signals.md) - [Автообновления безопасности (unattended-upgrades, dnf-automatic, livepatch)](/kb/unattended-upgrades.md)