
Как запустить серверное приложение как службу systemd
Объясняем службу и unit-файл на примере Node.js-приложения: отдельный пользователь, команда запуска, автозапуск, журнал и проверка порта.
Содержание
Эта инструкция нужна только приложению, у которого есть постоянно работающий серверный процесс: например, API на Node.js, Python-приложение, бот или обработчик задач. Если проект после сборки состоит из HTML, CSS, JavaScript и изображений, отдельную службу создавать не нужно — эти файлы уже раздаёт Nginx.
В примере приложение называется myapp, запускается командой /usr/bin/node /srv/myapp/server.js и принимает запросы только внутри VPS на порту 3000. Подставьте реальную команду своего проекта; копирование неизвестной точки входа не запустит другое приложение.
Что systemd добавляет к обычному запуску
Когда команда запущена в окне SSH, её легко случайно остановить вместе с терминалом. После перезагрузки VPS она сама не появится. Администратору также приходится отдельно выяснять номер процесса и место, куда записались ошибки.
Systemd — системная программа, которая запускает фоновые процессы Linux и следит за ними. Такой управляемый процесс называют службой. Правила одной службы хранятся в unit-файле: там записаны пользователь, рабочий каталог, команда запуска и поведение после сбоя.
После этого у приложения появляются одинаковые команды запуска, остановки и проверки, автоматический старт при загрузке VPS и журнал, связанный с именем службы.
Сначала найдите настоящую команду запуска
Unit-файл не угадывает, как устроен проект. Найдите команду, которой приложение уже запускается в разработке или по официальной документации. Для готового Node.js-приложения это может быть node server.js, для Python — запуск модуля через установленное виртуальное окружение.
Определите абсолютный путь к программе:
command -v node
Команда command -v показывает файл, который оболочка запускает по имени node. В примере ожидается /usr/bin/node. Systemd лучше передать полный путь, потому что его окружение отличается от привычного интерактивного терминала.
До создания службы запустите приложение вручную из его каталога и убедитесь, что оно не завершается с ошибкой. Остановите тест сочетанием Ctrl+C. Если обычная команда не работает, сначала исправьте зависимости и конфигурацию проекта: unit-файл не заменяет рабочий запуск.
Создайте отдельного пользователя для приложения
Серверному приложению редко нужны права администратора. Отдельный системный пользователь ограничивает файлы, которые процесс способен прочитать или изменить. У такого пользователя нет обычного пароля и интерактивного входа по SSH.
Создайте пользователя myapp и каталог приложения:
sudo useradd --system --home-dir /srv/myapp --shell /usr/sbin/nologin myapp
sudo install -d -o myapp -g myapp -m 750 /srv/myapp
Параметр --system создаёт служебную учётную запись, --home-dir задаёт её рабочее место, а оболочка nologin запрещает обычный интерактивный вход. Вторая команда создаёт /srv/myapp, назначает владельца и закрывает каталог от посторонних пользователей.
Поместите готовое приложение в этот каталог и назначьте пользователю только необходимые файлы. В учебном примере точка входа должна оказаться по пути /srv/myapp/server.js.
Проверьте запуск с ограниченными правами
Перед systemd выполните ту же команду от имени myapp. Так отдельно проверяются путь, зависимости и доступ к файлам:
sudo -u myapp /usr/bin/node /srv/myapp/server.js
sudo -u myapp меняет пользователя только для одной команды. Приложение должно запуститься и сообщить, что принимает соединения на ожидаемом адресе и порту. Проверьте его локально из второго SSH-окна, затем остановите тест через Ctrl+C.
Ошибка Permission denied означает, что пользователь не может пройти по каталогу, прочитать программу или открыть нужный файл. Cannot find module обычно указывает на неверный путь либо отсутствующую зависимость. Сначала добейтесь успешного ручного запуска; только затем добавляйте ещё один слой в виде systemd.
Создайте описание службы
Локальные unit-файлы администратора хранятся в /etc/systemd/system. Откройте новый файл:
sudoedit /etc/systemd/system/myapp.service
Добавьте минимальное описание:
[Unit]
Description=My server application
After=network.target
[Service]
Type=simple
User=myapp
Group=myapp
WorkingDirectory=/srv/myapp
ExecStart=/usr/bin/node /srv/myapp/server.js
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
Раздел [Unit] содержит понятное название и сообщает, что запуск происходит после подготовки базовой сети. Это не гарантирует доступность внешнего сервиса, а лишь задаёт порядок системных этапов.
В [Service] записана работа процесса. User и Group ограничивают права, WorkingDirectory задаёт текущий каталог приложения, а ExecStart — точную команду. Type=simple подходит программе, которая остаётся работать на переднем плане и не отделяет себя в фон. Restart=on-failure повторяет запуск после аварийного завершения, а RestartSec=5s делает паузу пять секунд.
Раздел [Install] нужен для автозапуска. multi-user.target — обычное рабочее состояние сервера без графического интерфейса. Название не относится к числу пользователей приложения.
Попросите systemd перечитать файл и запустите службу
После создания или изменения unit-файла systemd ещё не знает о новой версии. Перечитайте определения, запустите службу и сразу посмотрите её состояние:
sudo systemctl daemon-reload
sudo systemctl start myapp.service
sudo systemctl status myapp.service --no-pager
daemon-reload перечитывает unit-файлы и не перезагружает VPS. start запускает процесс сейчас. В состоянии исправной службы видна строка active (running), главный номер процесса и последние сообщения.
Если состояние failed, не повторяйте start много раз. Ниже статуса обычно находится первая ошибка программы. Полный журнал текущей попытки покажет команда:
sudo journalctl -u myapp.service -n 50 --no-pager
Параметр -u выбирает одну службу, -n 50 оставляет последние пятьдесят строк, а --no-pager выводит их прямо в терминал. Ищите первое сообщение о неверном пути, правах, занятом порте или отсутствующем файле.
Проверьте, что приложение действительно отвечает
Состояние running означает только наличие процесса. Приложение может работать, но отвечать ошибкой. Если оно должно принимать HTTP на локальном порту 3000, проверьте порт и запрос:
sudo ss -lntp | grep ':3000 '
curl -I http://127.0.0.1:3000/
Первая команда должна показать процесс, слушающий порт 3000. Адрес 127.0.0.1 означает доступ только внутри VPS — это подходящий вариант, когда внешние запросы принимает Nginx и передаёт их приложению. Не открывайте 3000 в UFW ради этой локальной проверки.
Вторая команда отправляет HTTP-запрос. Ожидаемый код зависит от приложения: главная страница может вернуть 200, API без такого маршрута — предусмотренный 404. Важно получить осмысленный ответ именно от приложения, а не отказ в соединении.
Связь локального порта с публичным доменом настраивается отдельным материалом о reverse proxy в Nginx.
Включите запуск после перезагрузки
Только после успешного состояния и прикладного запроса включите автозапуск:
sudo systemctl enable myapp.service
systemctl is-enabled myapp.service
Первая команда связывает службу с обычной загрузкой системы. Вторая должна вывести enabled. Это не запускает второй экземпляр: уже работающий процесс продолжит работу под контролем systemd.
Во время планового окна перезагрузите тестовый или новый VPS и снова проверьте systemctl status, локальный запрос и сайт через Nginx. Только такая проверка подтверждает автозапуск на практике.
Где хранить настройки приложения
Порт, режим работы и другие несекретные параметры можно задать в отдельном файле окружения. Пароли и токены тоже иногда хранят там, но файл должен быть вне репозитория, закрыт от других пользователей и включён в защищённую резервную копию.
Создайте файл, назначив чтение root и группе приложения:
sudo install -o root -g myapp -m 640 /dev/null /etc/myapp.env
sudoedit /etc/myapp.env
Добавьте строки вида PORT=3000 без слова export. Затем в раздел [Service] unit-файла добавьте EnvironmentFile=/etc/myapp.env, выполните systemctl daemon-reload и перезапустите службу.
Не помещайте секрет прямо в ExecStart: командную строку процесса могут видеть системные инструменты. Правила хранения ключей и смены скомпрометированного значения разобраны в статье о секретах на сервере.
Как применить изменение и вернуться назад
После замены кода systemd продолжает выполнять старый процесс, пока служба не будет перезапущена. В одном контролируемом цикле примените новую версию, подтвердите состояние процесса и повторите тот же локальный запрос, который проходил до обновления:
sudo systemctl restart myapp.service
systemctl is-active myapp.service
curl -I http://127.0.0.1:3000/
restart останавливает прежний процесс и запускает новый. is-active должен вывести active, после чего локальный запрос проверяет поведение. Если новая версия не запускается, верните прежние файлы или прежний каталог приложения и повторите перезапуск.
Systemd не хранит версии кода и не выполняет откат сам. Процедура публикации должна заранее сохранять предыдущую рабочую версию. Автоматизация этого процесса относится к отдельному этапу.
Служба готова, когда работает от непривилегированного пользователя, запускается после загрузки, слушает только нужный адрес, отвечает на проверочный запрос и пишет понятную причину сбоя в журнал. Следующая статья научит читать состояние systemd и журнал без случайного поиска по всему серверу.
Первоисточники
Самопроверка
Проверьте, что материал усвоен
Ответьте на все вопросы. Результат сохранится только в этом браузере и будет учтён в статистике прочитанных материалов.
Разбираем коротко
Частые вопросы
Нужна ли служба systemd статическому сайту?
Нет. Готовые HTML, CSS и изображения читает сам Nginx. Отдельная служба нужна программе, которая должна постоянно работать: API, серверному JavaScript-приложению, боту или обработчику очереди.
Почему приложение не стоит постоянно запускать вручную в SSH?
Ручной процесс зависит от терминала, не запускается автоматически после перезагрузки и не имеет единого состояния. Systemd запускает известную команду, следит за процессом и направляет его сообщения в журнал.
Всегда ли нужен Restart=always?
Нет. Для обычного приложения чаще подходит on-failure: systemd повторяет запуск после аварии, но уважает штатную остановку администратора. Постоянные перезапуски не исправляют причину сбоя.


