Таймеры systemd — встроенный в systemd механизм запуска задач по расписанию, альтернатива cron на дистрибутивах, где systemd уже управляет службами. Cron никуда не делся и продолжает работать, но у таймеров есть возможности, которых в cron нет: журналирование через journalctl, явные зависимости между задачами, повтор при сбое. Разбираем, когда переход на таймеры даёт реальное удобство, а когда cron проще оставить как есть.
Чем таймер systemd отличается от cron
Cron хранит расписание в одной строке текстового файла и запускает команду в указанное время, не зная ничего про то, что происходило до и после. Таймер systemd состоит из двух юнитов: .timer задаёт расписание, а .service описывает саму задачу так же, как для любой другой службы — с зависимостями, переменными окружения и пользователем, от имени которого она выполняется.
Это разделение означает, что одну и ту же задачу можно запустить вручную через systemctl, посмотреть её статус и последний результат — без необходимости искать запись в почте root или в отдельном логе, который сама задача должна была завести самостоятельно.
Когда таймер удобнее cron
Преимущество таймеров заметнее всего в нескольких конкретных ситуациях:
- Нужно видеть историю запусков задачи и код завершения без настройки отдельного логирования — всё уже в journalctl.
- Задача должна запуститься только после успешного старта другой службы, например после готовности базы данных.
- Нужен повтор при сбое: таймер можно настроить на перезапуск сервиса через заданный интервал после ошибки.
- Несколько задач конкурируют за одни и те же ресурсы, и нужно явно задать, что одна не запустится, пока не завершится другая.
- Расписание должно переживать пропущенный запуск из-за выключенного сервера — опция Persistent запускает задачу при следующей загрузке.
Как создать таймер и сервис
Минимальный комплект — два файла юнитов с одинаковым именем и разными расширениями: .service с самой командой и .timer с расписанием, после чего включается именно таймер, а не сервис напрямую.
# Включить и запустить таймер backup.timer
systemctl enable --now backup.timer
# Проверить статус и время следующего запуска
systemctl list-timers backup.timer
Логи и журнал таймера
Результат каждого запуска таймера попадает в journalctl под именем .service, а не рассылается почтой, как у cron. Это удобно для чтения в моменте, но у журнала systemd есть свой лимит объёма на диске, который со временем заполняется записями всех юнитов, не только таймеров.
Если задача сама пишет подробный лог в отдельный файл — например, вывод бэкапа или импорта, — для этого файла, а не для журнала systemd, нужна отдельная ротация, настроенная так же, как для любого другого растущего лога: это разобрано в статье про настройку logrotate.
Примеры применения: PHP-задачи и WordPress
Таймеры одинаково хорошо подходят и под собственные PHP-скрипты, запускаемые из командной строки, и под замену встроенного WP-Cron в WordPress на системное расписание — в обоих случаях задача описывается явной командой с конкретным путём к php-бинарю, а не вызовом через веб-сервер при каждом визите на сайт.
Разница между запуском PHP-скрипта из CLI и из-под FPM — в наборе переменных окружения и в правах пользователя, под которым идёт выполнение, и это важно учесть при переносе задачи в сервис-юнит; подробности — в статье про PHP CLI и FPM в cron. Готовый пример переноса самого WP-Cron с запуска по визитам на системное расписание есть в статье про системный cron для WP-Cron.
| Параметр | cron | Таймер systemd |
|---|---|---|
| Хранение расписания | строка в crontab | отдельный юнит .timer |
| Журнал запусков | почта root или свой лог | journalctl без донастройки |
| Зависимости между задачами | не поддерживаются | через параметры юнита |
| Повтор при сбое | не поддерживается | настраивается в .service |
Частые вопросы
Нужно ли удалять старые задания cron при переходе на таймеры?
Да, одну и ту же задачу не стоит держать одновременно в crontab и в юните таймера — она выполнится дважды и может привести к конфликту, например при одновременной записи в один файл. Переносить задачи стоит по одной, проверяя каждую перед удалением из cron.
Можно ли использовать таймеры в контейнере Docker?
Внутри обычного контейнера systemd как система инициализации, как правило, не запущен, поэтому таймеры там не работают так же, как на обычном сервере. Задачу по расписанию для контейнера логичнее запускать таймером systemd на хосте, который обращается к контейнеру снаружи, а не пытаться завести systemd внутри самого контейнера.
Что произойдёт, если сервер был выключен в момент запланированного запуска?
По умолчанию такой запуск просто пропадёт, как и в cron. Если нужно гарантировать выполнение задачи хотя бы раз за период, в юните .timer включается опция Persistent — тогда пропущенный запуск выполнится сразу после следующей загрузки системы.