Сервис в systemd «запускается и сразу падает», хотя вручную скрипт работает: тип сервиса выбран неправильно

Что было. Нужно было запускать серверное приложение при загрузке виртуальной машины. Написали юнит с Type=simple и ExecStart, указывающий на shell-скрипт. Скрипт запускал приложение в фоне и завершался. Systemd видел, что основной процесс завершился, считал сервис упавшим и останавливал всё, что тот успел породить.

Почему. При Type=simple systemd считает «главным» именно процесс из ExecStart. Если он уходит в фон и завершается, сервис для systemd закончился. Демонизация внутри скрипта и systemd работают друг против друга.

Что помогло автору. Type=oneshot вместе с RemainAfterExit=yes: скрипт выполняется один раз, а сервис остаётся в состоянии «активен» после его завершения. Дальше стандартные шаги: файл в /etc/systemd/system/, права на скрипт 755, systemctl daemon-reload, systemctl enable --now, проверка systemctl status.

Более правильный путь для постоянно работающих программ. Не демонизировать программу самостоятельно, а отдать её systemd на переднем плане: Type=simple (или exec) с ExecStart, указывающим прямо на исполняемый файл, плюс Restart=on-failure. Тогда systemd сам следит за процессом, перезапускает его и собирает журнал через journalctl -u имя. oneshot лучше оставить для разовых действий при загрузке.

Типовые ошибки в юнитах, из-за которых «вручную работает, при загрузке нет».

  1. Зависимость от сети: After=network-online.target и Wants=network-online.target, а не просто network.target, если программе нужен реальный доступ в сеть.
  2. Пустое окружение: systemd не читает ~/.bashrc; переменные задаются через Environment= или EnvironmentFile=, пути пишутся полностью.
  3. Рабочий каталог: WorkingDirectory=, иначе относительные пути в программе ведут в /.
  4. Пользователь: User=, чтобы приложение не работало от root.
  5. Читать journalctl -u имя -b: причина почти всегда написана там.

Параметры юнитов описаны в документации systemd.