Классическая ситуация: вы делаете systemctl start myapp — всё отлично. Но после reboot сервис либо не стартует, либо стартует и тут же уходит в failed, либо попадает в быстрый рестарт-цикл. В большинстве случаев это не «магия systemd», а комбинация раннего старта и агрессивной политики перезапуска.
Важно помнить: «после загрузки системы» — это не одна точка времени. Юниты и цели (targets) запускаются параллельно, а значит сервис может оказаться в фазе, где ещё не готовы сеть, DNS, монтирования или внешние зависимости.
Почему сервис «работает вручную», но ломается после reboot
Когда вы запускаете сервис вручную, вы обычно делаете это уже после того, как система «успокоилась»: интерфейсы получили адрес, резолвер настроен, диски примонтированы, секреты разложены. При старте на boot сервис может оказаться раньше этих событий.
Типовые причины:
- Сервис стартует слишком рано: ещё нет IP по DHCP, маршрута по умолчанию, DNS, поднятого VPN, доступного внешнего API.
- Юнит падает на старте и тут же перезапускается — пока не выбивает лимит StartLimit, после чего systemd перестаёт пытаться.
Быстрый чек-лист диагностики (до правок)
1) Посмотреть статус и понять симптом
systemctl status myapp.service
В статусе смотрим код выхода процесса, причину остановки, счётчик рестартов и сообщения про лимит запуска (StartLimit).
2) Логи конкретного юнита через journalctl
journalctl -u myapp.service -b
Ключ -b показывает логи текущей загрузки — это то, что нужно для «после reboot не взлетело».
Полезные варианты:
journalctl -u myapp.service -b -n 200
journalctl -u myapp.service -b -p warning
journalctl -u myapp.service --since "10 min ago"
Если сервис «мигнул» (успел стартовать и упасть), увеличьте количество строк и ищите первое исключение или первичную ошибку: temporary failure in name resolution, connection refused, no route to host, таймауты.
3) Понять, кто и когда запускает юнит
systemctl cat myapp.service
systemctl show myapp.service -p WantedBy -p After -p Wants -p Requires
systemctl cat показывает основной файл и drop-in overrides. systemctl show удобно читать как «плоскую» сводку зависимостей.
network-online.target: когда он нужен и почему After=network.target часто не спасает
Частая ошибка — поставить After=network.target и ждать, что «сеть готова». На деле network.target означает лишь, что сетевой стек и сетевой менеджер запущены. Интерфейсы могут ещё не получить адрес, маршрутизация может быть не готова, а DNS — не работать.
Если приложение на старте должно сразу подключиться к удалённой БД, очереди, API или сервис-дискавери, обычно нужен network-online.target.
network-online.target— это ожидание «сеть онлайн» в терминах вашего сетевого менеджера. systemd сам по себе не делает сеть онлайн: он лишь может подождать, если включён соответствующий wait-online юнит.
Что должно быть включено, чтобы ожидание работало
Зависит от того, чем управляется сеть:
- systemd-networkd: чаще всего
systemd-networkd-wait-online.service; - NetworkManager:
NetworkManager-wait-online.service; - прочие решения могут иметь свои механизмы ожидания.
systemctl status systemd-networkd-wait-online.service
systemctl status NetworkManager-wait-online.service
Практический минимум для сетевого сервиса
Универсальный базовый вариант: стартовать после «онлайна» и мягко подтягивать цель.
[Unit]
Description=MyApp
After=network-online.target
Wants=network-online.target
After=задаёт порядок.Wants=просит systemd попытаться активировать зависимость, но не превращает её в «жёсткое домино».

After=, Wants=, Requires=: как не устроить «загрузочный домино-эффект»
After= — только порядок, не запуск
After=postgresql.service означает: «мой сервис стартует после попытки запуска PostgreSQL». Но само по себе это не заставляет systemd запускать PostgreSQL.
Wants= — мягкая зависимость
Wants=postgresql.service означает: «когда запускают меня — попробуйте запустить и PostgreSQL». Если PostgreSQL не поднялся, мой сервис всё равно может попытаться стартовать (и уже сам решить, что делать).
Requires= — жёсткая зависимость
Requires=postgresql.service означает: «без PostgreSQL меня не должно быть». Если требуемый юнит упал — мой тоже остановится. Это удобно для обязательных локальных компонентов, но опасно для внешних/нестабильных зависимостей (сеть, удалённая БД, VPN): можно получить цепные остановки и долгие таймауты при загрузке.
Практическое правило
- Для сети чаще используйте
After=network-online.target+Wants=network-online.target. - Для локальных обязательных демонов (локальная БД, локальный агент) — рассмотрите
Requires=, но только понимая последствия.
Restart= и рестарт-циклы: самовосстановление без «само-DoS»
Если сервис падает из-за временной неготовности зависимостей (сеть/DNS/БД), перезапуск — нормальная стратегия. Но Restart=always без паузы и без контроля частоты быстро превращается в флаппинг: нагрузка, раздувание журнала и блокировка по лимитам запуска.
Выбор Restart= по смыслу
Restart=no— не перезапускать.Restart=on-failure— перезапускать при ошибке (часто лучший дефолт для продакшена).Restart=always— перезапускать даже при штатном выходе (уместно для воркеров, которые должны жить постоянно, но требует дисциплины).
RestartSec= — обязательная пауза
[Service]
Restart=on-failure
RestartSec=5s
Пауза даёт время DHCP/DNS/маршрутам и снижает вероятность «выжечь» лимиты.
StartLimitBurst и StartLimitIntervalSec
Если сервис слишком часто падает и перезапускается, systemd перестаёт запускать его и пишет что-то вроде start request repeated too quickly.
Часто параметры задают в [Unit]:
[Unit]
StartLimitIntervalSec=60
StartLimitBurst=10
- Если зависимость может подняться с задержкой, лучше делать более редкие попытки (увеличить
RestartSec) и умеренно поднять лимиты. - Если сервис падает из-за конфигурационной ошибки, лимиты лечить не нужно: исправляйте первопричину по журналу.
Паттерн «сеть/БД не готовы»: ожидание vs ретраи
Здесь две разные стратегии:
- Ожидание на уровне systemd: цели вроде
network-online.target, зависимости на локальные сервисы, зависимости на монтирование. - Ретраи на уровне приложения: приложение само повторяет подключение к БД/очереди с таймаутами и бэкоффом.
Хорошая практика: systemd обеспечивает правильный порядок и перезапуск, а приложение умеет переживать краткие провалы, не падая мгновенно.
ExecStartPre: короткая проверка перед стартом
Иногда полезно добавить быстрый pre-check, который явно покажет проблему в журнале и не даст запускаться «в пустоту». Главное — не превращать его в бесконечное ожидание.
[Service]
ExecStartPre=/usr/bin/getent hosts db.service.local
Как быстро находить первопричину через systemctl status
systemctl status полезен как «рамка»: кто запустил, что пытались, сколько раз перезапускали. Но первопричина почти всегда в journalctl -u.
Типовые строки:
Main process exited, code=exited, status=1/FAILURE— приложение вернуло ошибку (ищите конкретное сообщение в журнале выше).ExecStart=... (code=exited, status=203/EXEC)— systemd не смог выполнить файл (путь, права, отсутствует бинарник, неверныйExecStart).Start request repeated too quickly— сработали лимиты запуска; смотрите частоту падений,RestartSec,StartLimitBurst,StartLimitIntervalSec.
Связка команд «после reboot не взлетело»
systemctl status myapp.service
journalctl -u myapp.service -b -n 200
systemctl show myapp.service -p ActiveState -p SubState -p ExecMainStatus -p NRestarts
Пример юнита: нужна сеть и адекватное самовосстановление
Шаблон без «вечных ожиданий», но с корректной интеграцией в загрузку и сдержанными рестартами:
[Unit]
Description=MyApp API service
After=network-online.target
Wants=network-online.target
StartLimitIntervalSec=120
StartLimitBurst=12
[Service]
Type=simple
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/bin/myapp --config /etc/myapp/config.yml
Restart=on-failure
RestartSec=10s
TimeoutStartSec=30s
TimeoutStopSec=30s
[Install]
WantedBy=multi-user.target
WantedBy=multi-user.target— стандартная «серверная» фаза загрузки.After+Wantsнаnetwork-online.target— частый базовый минимум для сетевого демона.Restart=on-failureиRestartSec— не дёргаем сервис десятки раз в секунду.StartLimit*— защита от бесконечного флаппинга.

Включение автозапуска и проверка «как после reboot»
После правок:
systemctl daemon-reload
systemctl enable myapp.service
systemctl restart myapp.service
Проверить, что enable создал связи:
systemctl is-enabled myapp.service
systemctl list-dependencies --reverse myapp.service
Дальше лучший тест — реальная перезагрузка и проверка лога именно этой загрузки:
systemctl status myapp.service
journalctl -u myapp.service -b
Частые причины, которые маскируются под «проблемы сети»
- DNS ещё не готов: IP есть, но резолвер не работает. Симптом:
temporary failure in name resolution. - Маршруты или firewall: интерфейс поднят, но до цели не дойти. Симптомы:
no route to host, таймауты. - Нет нужных файлов/секретов: конфиг лежит на примонтированном томе. Тогда смотрите
RequiresMountsFor=и зависимости монтирования, а не толькоnetwork-online.target. - Права и пользователь: вручную запускали от root, а в юните указан
User=. Ошибка будет в журнале.
Мини-руководство по здравому смыслу для зависимостей systemd
Если сервис может стартовать без сети и подождать внутри себя, не перекладывайте бизнес-логику ожидания на systemd. Пусть systemd отвечает за порядок и перезапуск, а приложение — за ретраи и таймауты.
- Используйте
network-online.targetтолько там, где реально нужен «коннект сразу на старте». - Не злоупотребляйте
Requires=для внешних зависимостей. - Ставьте
Restart=on-failureи осмысленныйRestartSec. - Контролируйте флаппинг через
StartLimitBurstиStartLimitIntervalSec, но не лечите ими ошибки конфигурации. - Всегда начинайте отладку с
systemctl statusиjournalctl -u myapp.service -b.
Шпаргалка команд
systemctl status myapp.service
journalctl -u myapp.service -b -n 200
journalctl -u myapp.service -b -p err
systemctl cat myapp.service
systemctl show myapp.service -p After -p Wants -p Requires -p NRestarts
systemctl daemon-reload
systemctl enable myapp.service
systemctl restart myapp.service
Полезная перелинковка по теме надёжности
Если ваш сервис зависит от PostgreSQL, часто проблема «после reboot» на деле оказывается перегрузкой соединений или долгим прогревом базы. В таком случае пригодятся:
- гайд по PgBouncer и пуллингу соединений к PostgreSQL
- практика настройки autovacuum и индексов в PostgreSQL
И отдельно: если вы отлаживаете подобное на отдельной машине (стенд/прод с разными профилями сети и DNS), удобнее держать сервисы в изолированной среде на VDS, чтобы воспроизводить проблему «как после reboot» без влияния соседей по хосту.


