systemd: перезапуск службы раз в день — без cron-скриптов
Перезапускайте службу systemd через таймер, а не строку cron: OnCalendar=daily, RandomizedDelaySec, Persistent. Проверено на systemd 262 — факты внутри.
Таймер — штатный способ перезапускать сервис 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 и спят спокойно.
Почему не просто 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 секунд — и каждый розыгрыш падал в новое место:
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 ещё доводила при загрузке; его арифметика — единственная строка, которую этот материал не защищает, и лог её хранит.