SYSTEMD · 2026-11-03

systemd: перезапуск службы раз в день — без cron-скриптов

Перезапускайте службу systemd через таймер, а не строку cron: OnCalendar=daily, RandomizedDelaySec, Persistent. Проверено на systemd 262 — факты внутри.

HUMANавтор @mrkeepalive1h6 мин чтения

Таймер — штатный способ перезапускать сервис systemd по расписанию. OnCalendar=daily выбирает полночь, RandomizedDelaySec размазывает нагрузку после 00:00, Persistent=true догоняет упущенное после простоя. Cron может выполнить тот же перезапуск — но вслепую, ровно в полночь, в очереди со всеми остальными заданиями машины и без единой записи о том, когда он срабатывал в последний раз. На сегодняшнем стенде таймер сработал трижды за девяносто пять секунд, и журнал назвал каждый запуск с точностью до десятой доли секунды.

Как выглядит рецепт ежедневного перезапуска?

Два файла. Один знает единственный трюк — перезапустить приложение. Второй решает, когда. Ни один не трогает юнит самого приложения.

# /etc/systemd/system/app-restart.service
[Unit]
Description=scheduled restart of app

[Service]
Type=oneshot
ExecStart=/usr/bin/systemctl try-restart app.service

# /etc/systemd/system/app-restart.timer
[Unit]
Description=restart app once a day

[Timer]
OnCalendar=daily
RandomizedDelaySec=45m
Persistent=true

[Install]
WantedBy=timers.target

проверка: sudo systemctl daemon-reload && sudo systemctl enable --now app-restart.timer && systemctl list-timers app-restart.timer — заполненная колонка NEXT означает, что расписанием теперь владеет таймер.

try-restart, а не restart: если приложение вдруг остановлено, обычный restart молча его запустит — возможно, воскресив то, что вы остановили намеренно. try-restart трогает только уже работающее. Сервисы, которым нельзя оставаться остановленными, меняют его на restart и спят спокойно.

OnCalendar=dailyкалендарное колесо · 00:00 RandomizedDelaySecдо 45 мин, разыгрывается на каждый запуск Persistent=trueштамп · пропущенные дни догонят при загрузке oneshot · try-restartтолько если приложение работает приложение вернулось, свежеетот же юнит, новый PID
Пять подвижных частей, один перезапуск в день. Штамп — единственное состояние на диске: файл нулевого размера.

Почему не просто cron?

Потому что костыль cron работает один раз, а потом опирается на вас. @daily systemctl restart app — одна строка в crontab root, и тут же начинаются вопросы. Когда срабатывал в последний раз? Сработал ли вообще, пока машина была выключена? Почему все задания машины поставили штамп 00:00:00 разом? Man-страница cron определяет @daily как ровно полночь; каждый сервис с таким расписанием проходит один и тот же турникет в одну и ту же секунду, и первый, кто проходит, — это диск.

Честный итог: cron перезапускает приложение, и на этом всё. Ни колонки list-timers, ни штампа на диске, ни догоняющего запуска после простоя, ни юнита в журнале, который можно почитать — если демон cron вообще установлен. В базовом образе нашего стендового VM его нет. На современной машине с Arch таймеры уже ждут под PID 1.

вопросcron @dailyтаймер
Когда срабатывал в последний раз?grep по логам, если повезётlist-timers, колонка LAST
Машина была выключена в полночь?пропущено, молчаPersistent догонит при загрузке
Ровно когда выполняется?00:00:00 точно00:00 плюс разыгранная задержка
Лишний демон в хозяйстве?cron или crondникакого — PID 1 уже умеет
Где логи?письмом, если вообщеjournalctl -u, структурированно

Общий случай — когда таймер бьёт строку cron и почему — живёт в нашем прошлом материале, Cron против таймеров systemd: cron выполняет строки, таймеры выполняют юниты. Этот — лишь частный случай ночного перезапуска.

Как разогнать полночное столпотворение?

Одной строкой: RandomizedDelaySec=45m. После каждого срабатывания календаря таймер разыгрывает новую случайную задержку, и дюжина ночных перезапусков ложится в любые точки окна, а не в один мгновенный столб. Стенд гонял уменьшенную до минут версию рецепта — календарь на :30, задержка с потолком в 20 секунд — и каждый розыгрыш падал в новое место:

квитанции стенда, без правок — systemd 262 на Arch
Nov 03 05:13:48.974739 bench-arch systemd[1]: Starting scheduled restart of app...
RandomizedDelayUSec=20s
LastTriggerUSec=Tue 2026-11-03 05:14:40 UTC
NextElapseUSecRealtime=Tue 2026-11-03 05:15:43 UTC

Читайте арифметику по строкам: календарь 30, потолок 20 — розыгрыши вышли на 18,97, 10,42 и 13 секунд после. Три срабатывания, три разные секунды, все внутри потолка. Cron разыгрывать не умеет: его man планирует на фиксированную секунду, навсегда. Верните 45m — и ночные перезапуски размажутся по всему окну, а диск больше не встретит толпу.

А если машина была выключена в момент перезапуска?

Persistent=true — это оговорка о догоне. После каждого срабатывания таймер касается штампа нулевого размера — /var/lib/systemd/timers/stamp-app-restart.timer — и systemd при загрузке сверяет его с календарём. Машина выключена всю ночь, загрузка в 09:00: пропущенный перезапуск выполняется немедленно, один раз. Без этой строки пропущенный запуск молча пропускается, и приложение ждёт своего сброса целый день. Штамп — обещание нулевого размера: за мной один перезапуск после следующей загрузки.

каталог штампов на стенде, без правок
-rw-r--r-- 1 root root 0 Nov  3 05:13 stamp-app-restart.timer
-rw-r--r-- 1 root root 0 Oct 24 10:42 stamp-archlinux-keyring-wkd-sync.timer

Вторая строка не наша: в базовом образе этот таймер уже включён и уже отмечен. Паттерн — привычка самого штатного планировщика: обновления keyring в Arch точно так же догоняют упущенное после простоя, как это сделает ваше приложение.

Разве Restart=always — не то же самое?

Нет, и путаница между ними — так рождаются мифические ночные отказы. Restart=always управляется отказом: главный процесс завершился, systemd поднимает его снова через RestartSec. Пока приложение живо и здорово, он не срабатывает вовсе — реагировать не на что. Календарным обслуживанием он заниматься не может, потому что здоровье — не дата. Таймер управляется календарём: наступает 00:00 — перезапуск выполняется, здорово приложение или нет. Держите оба, если нужны оба: Restart=always против падений, таймер — против ночного сброса. Они следят за разными часами и никогда не дерутся.

Как проверить, что он вообще сработал?

systemctl list-timers app-restart.timer --no-pager
journalctl -u app-restart.service --since today --no-pager

проверка: после полуночи NEXT заполняется заново на завтра, а LAST сидит внутри вашего окна задержки. Нужная строка журнала — Starting scheduled restart of app... — за ней следуют собственные остановка и старт приложения:

собственный журнал приложения вокруг срабатывания на стенде, без правок
Nov 03 05:13:48.990989 bench-arch systemd[1]: Stopping the app that gets restarted nightly...
Nov 03 05:13:48.993916 bench-arch systemd[1]: Started the app that gets restarted nightly.

Ночной перезапуск — это признание: что-то течёт, и дыру ещё никто не нашёл.

Переведите свой сервис на таймер сегодня вечером по чек-листу и позвольте ему нести признание, пока кто-нибудь не починит течь по-настоящему — потом удалите оба юнита и дайте машине спать сквозь полночь. Это условие выхода, и файл-штамп по вам скучать не будет.

Методика стенда — чтобы прочитать сырые квитанции

Одноразовый VM с Arch (cloud-образ quickemu, без изменений на хосте): один app.service с sleep infinity, рецепт таймера уменьшен до OnCalendar=*-*-* *:*:30 с RandomizedDelaySec=20, чтобы срабатывание укладывалось в минуту ожидания, затем 95 секунд наблюдения за журналом. Три срабатывания, файл-штамп, колонки list-timers и версия systemd — в одной квитанции-логе, напечатанной без правок выше. Третье срабатывание на стенде пришлось на часы, которые VM ещё доводила при загрузке; его арифметика — единственная строка, которую этот материал не защищает, и лог её хранит.