STEADRUN

systemd timer вместо cron

Обновлено 07.11.2026

Таймер systemd, как и строка в crontab, запускает команду по расписанию. Файлов два вместо одной строки, зато вывод задачи сохраняется в журнал, а запуск, пропущенный из-за выключенного сервера, выполнится после включения. Чаще всего при переносе ошибаются в расписании: у OnCalendar свой формат, и одно правило cron в нём работает иначе.

Чем таймер лучше cron?

  • Вывод попадает в журнал. Всё, что команда печатает, лежит в journalctl -u backup.service с временем и кодом выхода. В cron вывод пропадает, если не перенаправить его в файл.
  • Пропущенный запуск догоняется. С Persistent=true задача, которая должна была сработать, пока сервер был выключен, запустится сразу после включения. Cron такие запуски просто пропускает.
  • Запуски не наслаиваются. Если служба ещё работает, когда таймер срабатывает снова, второй экземпляр не стартует. В cron для этого нужен flock.
  • Окружение задано явно. Пользователь и переменные описаны в файле службы. Команда не упадёт из-за короткого PATH, как бывает в cron.
  • Видно, когда следующий запуск. systemctl list-timers показывает все таймеры с временем прошлого и следующего срабатывания.

Cron проще, когда задача одна и короткая или когда systemd нет, например в Docker-контейнере.

Как перенести задачу из crontab?

Допустим, в crontab такая строка:

0 3 * * * /home/deploy/backup.sh

Создайте службу /etc/systemd/system/backup.service. Она описывает, что запускать:

[Unit]
Description=Ночной бэкап базы

[Service]
Type=oneshot
User=deploy
ExecStart=/home/deploy/backup.sh

И таймер /etc/systemd/system/backup.timer с тем же именем. Он описывает, когда запускать:

[Unit]
Description=Ночной бэкап базы в 03:00

[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true

[Install]
WantedBy=timers.target

Включите таймер и уберите строку из crontab, иначе задача будет запускаться дважды:

sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
crontab -e   # удалите старую строку

Таймер запускает службу с таким же именем, поэтому backup.timer запустит backup.service. Другое имя можно указать в Unit=.

Как перевести cron-выражение в OnCalendar?

Формат OnCalendar — день-недели год-месяц-число часы:минуты:секунды. День недели можно не указывать, * значит «любое значение», диапазон пишется через .., шаг через /.

cron OnCalendar Когда
* * * * * *-*-* *:*:00 каждую минуту
*/5 * * * * *-*-* *:00/5:00 каждые 5 минут
0 * * * * *-*-* *:00:00 или hourly каждый час
15 */2 * * * *-*-* 00/2:15:00 каждые 2 часа в 15 минут
0 3 * * * *-*-* 03:00:00 каждый день в 03:00
30 8 * * 1-5 Mon..Fri *-*-* 08:30:00 по будням в 08:30
0 0 * * 1 Mon *-*-* 00:00:00 по понедельникам в полночь
0 9 1 * * *-*-01 09:00:00 1-го числа в 09:00
0 0 1 */3 * *-01/3-01 00:00:00 раз в квартал

Готовая строка для любого выражения есть в инструменте «Cron-выражение»: впишите выражение, и в блоке «В других планировщиках» появится OnCalendar. Там же записи для Laravel, Kubernetes и GitHub Actions.

Если в cron заданы и день месяца, и день недели (0 0 1 * 1), задача запускается, когда совпало любое из двух: 1-го числа или в понедельник. В OnCalendar должны совпасть оба условия. Чтобы получить поведение cron, укажите две строки OnCalendar= в одном таймере: одну с числом, другую с днём недели. Таймер сработает по любой из них.

Проверить строку можно до того, как ставить таймер:

systemd-analyze calendar --iterations=5 "Mon..Fri *-*-* 08:30:00"

Команда покажет нормализованную запись и пять ближайших запусков, а при ошибке в строке напишет, где она.

В каком часовом поясе считается OnCalendar?

В часовом поясе сервера, как и cron. Посмотреть его можно командой timedatectl. Если сервер в UTC, а задача нужна в 03:00 по Москве, допишите пояс в конец строки:

OnCalendar=*-*-* 03:00:00 Europe/Moscow

Так расписание не съедет, если пояс сервера когда-нибудь поменяют.

Как проверить, что таймер работает?

systemctl list-timers backup.timer            # когда прошлый и следующий запуск
systemctl status backup.service               # чем закончился последний запуск
journalctl -u backup.service --since today    # вывод задачи
sudo systemctl start backup.service           # запустить вручную, не дожидаясь расписания

Если в list-timers таймера нет, он не включён: проверьте systemctl is-enabled backup.timer.

Частые ошибки

  • Забыли daemon-reload. После правки файлов systemd продолжает работать со старой версией, пока не выполнить sudo systemctl daemon-reload.
  • Включили службу вместо таймера. systemctl enable backup.service ничего не запланирует. Включать нужно backup.timer.
  • Нет Type=oneshot. Без него служба считается запущенной, как только процесс стартовал, и systemctl start не ждёт конца задачи. Для задач по расписанию ставьте oneshot.
  • Запуск на минуту позже. По умолчанию AccuracySec=1min: systemd может сдвинуть срабатывание в пределах минуты, чтобы объединить пробуждения. Если важна точность до секунды, поставьте AccuracySec=1s.
  • Persistent=true там, где догонять не нужно. После простоя сервера таймер сразу запустит пропущенное. Бэкапу это и нужно, а рассылка клиентам уйдёт в момент включения сервера, даже если это ночь.
  • Пояс указан в проверке, а не в таймере. Если Вы следите за задачей в Steadrun, часовой пояс проверки должен совпадать с поясом, в котором считается OnCalendar.

Как узнать, что задача не выполнилась?

Упавшую службу видно в systemctl status и в журнале, но сообщать об этом systemd не умеет. А если таймер выключили или сервер лежит, в журнал вообще ничего не попадёт.

Чтобы узнавать о таких сбоях, задача должна отмечаться после каждого успешного запуска, а кто-то должен ждать этой отметки. Так работает Steadrun. Добавьте в службу пинг после основной команды:

[Service]
Type=oneshot
User=deploy
ExecStart=/home/deploy/backup.sh
ExecStartPost=-/usr/bin/curl -fsS -m 10 --retry 3 -o /dev/null https://steadrun.ru/ping/ВАШ-UUID

ExecStartPost в службе oneshot выполняется, только если ExecStart завершился успешно. Минус перед путём значит, что сбой самого curl не помечает службу упавшей. В Steadrun создайте проверку с выражением 0 3 * * * и тем же часовым поясом. Если пинг не придёт к сроку, Вы получите алерт в Telegram, на почту или на вебхук.

Вариант, который сразу сообщает о падении и присылает хвост вывода, есть в рецептах. Как подключить проверку с нуля, описано в быстром старте. Бесплатно можно следить за пятью задачами.