Выберите продукт

Debian/Ubuntu: как исправить systemd service holdoff time over и restart counter is at

Если сервис в Debian или Ubuntu уходит в цикл перезапусков, systemd показывает holdoff time over и restart counter is at. Разберём, как быстро найти причину через systemctl status и journalctl, что означают коды ошибок и когда действительно стоит менять Restart и лимиты запуска.
Debian/Ubuntu: как исправить systemd service holdoff time over и restart counter is at

Сообщения service holdoff time over и restart counter is at в Debian/Ubuntu почти всегда означают одно: systemd пытается автоматически поднять сервис после сбоя, но процесс снова завершается с ошибкой. В итоге получается типичный restart loop — цикл перезапусков, который либо продолжается, либо уже остановлен защитными лимитами менеджера сервисов.

На практике проблема редко бывает в самом systemd. Обычно это только симптом. Истинная причина почти всегда ниже уровнем: неверный путь к бинарнику, ошибка в конфиге, отсутствующий каталог, неправильный пользователь, занятый порт, недоступная база данных, неверные переменные окружения или слишком агрессивные параметры рестарта.

Частая ошибка администраторов — сразу править Restart=always, RestartSec или увеличивать лимиты, не выяснив, почему процесс вообще падает. Это маскирует симптом, но не лечит сервис.

Ниже разберём, что означают строки holdoff time over и restart counter is at, как правильно читать вывод systemctl status, какие команды запускать через journalctl, как отделить проблему unit-файла от ошибки приложения и когда действительно имеет смысл менять Restart, RestartSec, StartLimitBurst и StartLimitIntervalSec.

Материал особенно полезен для сервисов собственного развёртывания: Node.js, Python, Go, Java, очередей, API-демонов и любых процессов, которые запускаются через unit-файл в /etc/systemd/system. Если вы поднимаете такие приложения на VDS, этот сценарий встретится рано или поздно почти наверняка.

Что означают сообщения holdoff time over и restart counter is at

Когда systemd запускает сервис с политикой автоматического перезапуска, он отслеживает число неудачных стартов и выдерживает паузу между попытками. Эта пауза задаётся параметром RestartSec. После неё в журнале и появляется строка вида Service hold-off time over, scheduling restart. Это не отдельная ошибка, а уведомление: время ожидания истекло, менеджер собирается снова стартовать юнит.

Сообщение restart counter is at N означает, что systemd уже несколько раз пытался перезапустить сервис и ведёт счётчик таких попыток. Если этот счётчик превысит разрешённый порог в заданный интервал, сработает защита от бесконечного флаппинга, и вы увидите уже другой итоговый симптом — отказ в запуске из-за превышения лимита стартов.

holdoff time over — это следствие политики рестарта, а restart counter is at — индикатор того, что сервис вошёл в повторяющийся цикл падений.

Поэтому главный вопрос не в том, как убрать эти строки из журнала, а в том, что именно заставляет процесс завершаться. Если приложение стартует и сразу выходит с кодом 1, systemd будет послушно перезапускать его, пока не упрётся в лимиты.

С чего начинать диагностику

Первое правило: не смотреть только на состояние «active/inactive». Важны последние строки статуса, код выхода, причина завершения и журнал юнита.

Начните с базовой проверки:

systemctl status myservice.service --no-pager -l
systemctl show myservice.service -p ExecMainStatus -p ExecMainCode -p Result -p Restart -p RestartUSec
journalctl -u myservice.service -b --no-pager -n 100

Что искать в выводе:

  • строку Main PID и чем завершился процесс;
  • status=1/FAILURE, status=203/EXEC, status=217/USER и другие коды;
  • сообщения приложения прямо перед завершением;
  • повторяющиеся рестарты с одинаковым интервалом;
  • упоминание превышения лимита стартов.

Очень полезно смотреть журнал только за текущую загрузку системы. Ключ -b отсекает старый шум и показывает актуальную картину. Если сервис уже давно флапает, сузьте интервал:

journalctl -u myservice.service --since "10 minutes ago" --no-pager
journalctl -xeu myservice.service --no-pager

Команда systemctl status хороша для краткой сводки, но почти всегда недостаточна. Основная фактура лежит в journalctl.

Просмотр журнала systemd-сервиса через journalctl

Типичный сценарий: systemd исправен, сломан сам процесс

В большинстве случаев юнит написан формально правильно, а сервис падает из-за своей внутренней ошибки. Например, приложение ждёт файл конфигурации в /opt/app/config.yml, а файл отсутствует. Или пытается слушать порт, который уже занят другим процессом. Или стартует от пользователя, у которого нет доступа к рабочему каталогу.

Вот самые частые реальные причины:

  • неверный путь в ExecStart;
  • скрипт не имеет права на исполнение;
  • не существует пользователя из параметра User;
  • не существует каталога из WorkingDirectory;
  • приложение пишет PID, сокет или логи туда, где нет прав;
  • процесс требует переменные окружения, которых нет в systemd-окружении;
  • конфиг приложения содержит синтаксическую ошибку;
  • порт уже занят, сокет не создаётся;
  • сервис зависит от сети, DNS, базы данных или другого юнита, который ещё не готов;
  • неправильно выбран тип юнита: например, указан Type=forking для процесса, который не форкается.

Если в журнале нет ясной причины, попробуйте запустить команду из ExecStart вручную от того же пользователя и из того же каталога. Это один из самых быстрых способов вскрыть расхождение между «работает в shell» и «не работает в systemd».

systemctl cat myservice.service
id appuser
sudo -u appuser sh -lc 'cd /opt/myapp && /opt/myapp/bin/start-app'

Если вручную команда сразу показывает ошибку, вы нашли корень проблемы. Если вручную всё работает, а через systemd нет — проверяйте окружение, Type, зависимости и ограничения юнита.

FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

Как читать коды ошибок systemd

Некоторые коды выхода особенно полезны при разборе циклов рестарта. Они помогают понять, ошибка возникла ещё до запуска приложения или уже внутри него.

status=203/EXEC

Обычно это значит, что systemd не смог выполнить команду из ExecStart. Причины типовые: неверный путь, отсутствие файла, нет бита исполнения, плохой shebang в скрипте, неподходящая архитектура бинарника.

ls -l /opt/myapp/bin/start-app
file /opt/myapp/bin/start-app
head -n 1 /opt/myapp/bin/start-app

status=217/USER

Чаще всего это проблема с параметром User или Group. Пользователь не существует, NSS не готов, либо systemd не может переключить контекст запуска.

getent passwd appuser
getent group appgroup

status=1/FAILURE или другой код приложения

Здесь systemd обычно ни при чём: бинарник запустился и сам завершился с ошибкой. Значит, нужен лог приложения, проверка конфигурации и ручной запуск.

Result=start-limit-hit

Это уже следствие, а не причина. Сервис падал слишком часто, и systemd прекратил попытки запуска до ручного вмешательства или до истечения лимитного окна.

Параметры Restart, RestartSec, StartLimitBurst и StartLimitIntervalSec

Именно эти параметры чаще всего всплывают рядом с ошибками holdoff time over и restart counter is at. Важно понимать их смысл по отдельности.

Restart=

Параметр определяет, когда сервис надо перезапускать.

  • no — не перезапускать;
  • on-failure — перезапускать при ошибке, ненулевом коде или аварийном завершении;
  • always — перезапускать почти всегда, даже после «успешного» выхода процесса.

Для большинства демонов и фоновых приложений разумнее начинать с Restart=on-failure. Параметр Restart=always полезен не всегда: если приложение по дизайну должно иногда завершаться штатно, вы получите ложный цикл рестартов.

RestartSec=

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

На практике часто подходят значения от 2 до 10 секунд. Для зависимостей, которые могут подниматься дольше, иногда разумнее 15–30 секунд.

StartLimitBurst=

Задаёт, сколько неудачных стартов допустимо в пределах окна. Когда лимит исчерпан, systemd перестаёт автоматически рестартовать юнит.

StartLimitIntervalSec=

Это размер окна, в течение которого считается число неудачных запусков. Например, если задано StartLimitBurst=5 и StartLimitIntervalSec=60, то после пяти неудачных стартов за минуту сервис упрётся в лимит.

Эти параметры не лечат приложение. Они только защищают систему от бесконечного и шумного restart loop.

Пример корректировки unit-файла

Допустим, сервис стартует слишком рано и падает, если внешняя зависимость ещё не готова. В таком случае можно аккуратно скорректировать unit:

[Unit]
Description=My App
After=network-online.target
Wants=network-online.target
StartLimitIntervalSec=60
StartLimitBurst=5

[Service]
Type=simple
User=appuser
Group=appuser
WorkingDirectory=/opt/myapp
EnvironmentFile=/etc/default/myapp
ExecStart=/opt/myapp/bin/myapp
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

После изменения unit-файла не забывайте перечитать конфигурацию менеджера и сбросить счётчик неудачных стартов, если лимит уже достигнут:

systemctl daemon-reload
systemctl reset-failed myservice.service
systemctl restart myservice.service
systemctl status myservice.service --no-pager -l

Команда reset-failed особенно важна. Иначе вы можете исправить конфиг, но юнит останется в состоянии отказа из-за ранее сработавшего лимита.

Если у вас несколько фоновых воркеров или обработчиков очередей, полезно также посмотреть, как организован запуск воркеров через Supervisor и systemd.

Редактирование unit-файла systemd с параметрами Restart и StartLimit

Когда менять лимиты запуска, а когда не надо

Очень соблазнительно просто увеличить StartLimitBurst и надеяться, что сервис «как-нибудь поднимется». Иногда это допустимо, но только если вы уверены, что причина временная: например, после загрузки система ждёт сеть, удалённое хранилище или базу данных.

Если же приложение падает из-за синтаксической ошибки в конфиге, рост лимитов ничего не даст. Вы лишь получите больше одинаковых падений в журнале.

Сначала устраняют первопричину падения процесса, и только потом подбирают политику рестарта. Не наоборот.

Изменять лимиты обычно уместно в таких случаях:

  • сервис зависит от внешней БД или API, которые могут быть недоступны короткое время;
  • после reboot сеть или DNS действительно поднимаются не мгновенно;
  • приложению нужен больший интервал между повторными подключениями;
  • вы осознанно хотите ограничить скорость флаппинга.

Не стоит менять лимиты как первое действие, если:

  • в журнале уже есть чёткая ошибка запуска;
  • ExecStart указывает на несуществующий файл;
  • у процесса нет прав на каталог или сокет;
  • неверно задан пользователь;
  • приложение завершается сразу после старта из-за собственного бага.

Полезный алгоритм поиска причины за 10 минут

  1. Посмотрите systemctl status и зафиксируйте код ошибки, Result и последние строки лога.
  2. Откройте журнал через journalctl -u myservice.service -b -n 100.
  3. Найдите первую ошибку приложения, а не только сообщения о рестартах.
  4. Проверьте unit через systemctl cat и убедитесь, что верны ExecStart, User, Group, WorkingDirectory, EnvironmentFile.
  5. Запустите команду вручную от того же пользователя.
  6. Проверьте зависимости: сеть, сокеты, каталоги, порты, базы данных.
  7. Только после этого корректируйте Restart, RestartSec, StartLimitBurst и StartLimitIntervalSec.
  8. Сделайте systemctl daemon-reload, затем systemctl reset-failed и повторный запуск.

Частые ошибки в unit-файлах, которые запускают restart loop

Неподходящий Type=

Если приложение работает в foreground, обычно нужен Type=simple или иногда Type=exec. Если указать Type=forking для обычного процесса без демонизации, systemd будет неверно интерпретировать его поведение.

Shell-конструкции прямо в ExecStart

Systemd не исполняет строку как shell-команду автоматически. Если вы вставили туда сложную shell-логику без явного вызова интерпретатора, поведение может оказаться не тем, что ожидается.

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

Зависимость от интерактивного окружения

Команда работает у вас в SSH-сессии, потому что там есть PATH, переменные, домашний каталог и другие привычные условия. Под systemd всего этого может не быть. Явно указывайте полные пути и нужные переменные через Environment= или EnvironmentFile=.

Сервис успешно завершился, но задан Restart=always

Это часто бывает со скриптами одноразовой инициализации. Они отрабатывают корректно, выходят с кодом 0, а systemd запускает их снова и снова. Для таких задач нужен либо другой тип юнита, либо отказ от постоянного рестарта.

Как понять, что проблема в порядке запуска после reboot

Если после ручного systemctl restart сервис стартует нормально, а после перезагрузки уходит в цикл рестартов, почти наверняка проблема в зависимостях и порядке инициализации. Обычно это сеть, DNS, монтирования или база данных.

Проверьте, есть ли в unit-файле корректные зависимости:

[Unit]
After=network-online.target
Wants=network-online.target

Но не переоценивайте эти директивы. Они не гарантируют, что удалённый API, PostgreSQL, MySQL, Redis или внешний endpoint уже готовы принимать соединения. Иногда правильнее увеличить RestartSec и сделать приложение устойчивым к временной недоступности зависимостей. Для связанных тем может пригодиться статья про усиление и изоляцию systemd-сервисов на VDS.

Виртуальный хостинг FastFox
Виртуальный хостинг для сайтов
Универсальное решение для создания и размещения сайтов любой сложности в Интернете от 95₽ / мес

Что делать после исправления

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

systemctl restart myservice.service
systemctl is-active myservice.service
systemctl show myservice.service -p ActiveState -p SubState -p NRestarts
journalctl -u myservice.service --since "5 minutes ago" --no-pager

Поле NRestarts удобно для быстрой проверки: если число продолжает расти, цикл рестартов не устранён до конца. Также полезно перепроверить, не остались ли старые ошибки приложения в собственном лог-файле, если оно пишет не только в journald.

Краткие выводы

Сообщения systemd service holdoff time over и restart counter is at не являются корнем проблемы. Это маркеры того, что сервис завершился неуспешно и systemd пытается восстановить его согласно политике рестарта.

Главные инструменты для разбора — systemctl status и journalctl. Они позволяют быстро отделить ошибку unit-файла от ошибки самого приложения. Параметры Restart, RestartSec, StartLimitBurst и StartLimitIntervalSec важны, но настраивать их нужно только после понимания первопричины.

Если говорить совсем практично, правильный порядок такой: посмотреть журнал, воспроизвести запуск вручную, исправить unit или приложение, сбросить счётчик через systemctl reset-failed, затем проверить стабильность. В большинстве случаев этого достаточно, чтобы убрать restart loop без шаманства и бесконечного перебора параметров.

Поделиться статьей

Вам будет интересно

Debian/Ubuntu: RTNETLINK answers: File exists — как исправить ошибки IP, route и netplan OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: RTNETLINK answers: File exists — как исправить ошибки IP, route и netplan

Ошибка RTNETLINK answers: File exists в Debian и Ubuntu обычно означает, что IP-адрес, маршрут или правило уже существуют. Показыв ...
Debian/Ubuntu: как исправить Permission denied между Nginx и PHP-FPM через Unix socket OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: как исправить Permission denied между Nginx и PHP-FPM через Unix socket

Ошибка connect() to unix failed (13: Permission denied) между Nginx и PHP-FPM в Debian и Ubuntu обычно связана с правами на сокет, ...
Debian/Ubuntu: rp_filter, reverse path filtering и policy routing без сюрпризов OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: rp_filter, reverse path filtering и policy routing без сюрпризов

Разбираем, как в Debian/Ubuntu работает reverse path filtering, почему в dmesg появляются martian source и как правильно сочетать ...