Как создать systemd сервис для программы на VPS
Узнайте, как превратить скрипт в systemd сервис для автозапуска при загрузке и перезапуска при сбоях. Инструкция по написанию unit-файла, настройке таймеров и прав доступа.
Что такое systemd-сервис и зачем он нужен
systemd-сервис — это небольшой текстовый файл, который указывает серверу, как запускать программу: выполнять автозапуск при загрузке, перезапускать её в случае сбоя и направлять вывод в системный журнал. Это вся его задача. Программа, запущенная вручную в SSH-сессии, завершается в момент выхода из системы или перезагрузки сервера. Программа, оформленная как systemd-сервис, продолжает работать, так как её владельцем становится сам сервер, а не ваша оболочка.
systemd — это система инициализации в Ubuntu, Debian, Fedora и большинстве современных Linux-серверов. Это первый процесс, который запускается при старте, и именно он контролирует всё остальное. Так было не всегда, и историю о том, как systemd заменил предыдущие скрипты инициализации стоит прочитать, когда вы поймёте, что делает unit-файл. Создавая файл сервиса, вы передаёте свою программу под управление этого супервизора. В этом руководстве показан минимально рабочий unit, три секции, которые есть у каждого unit-файла, способы включения сервиса и чтения его логов, запуск по расписанию с помощью таймеров, а также методы ограничения прав для обеспечения минимально необходимых привилегий.
Минимально рабочий сервис
Файл сервиса располагается в /etc/systemd/system/, имеет расширение .service и требует всего несколько строк. Создайте такой файл для программы, находящейся в /usr/local/bin/myapp:
sudo nano /etc/systemd/system/myapp.service[Unit]
Description=My application
[Service]
ExecStart=/usr/local/bin/myapp
[Install]
WantedBy=multi-user.targetЭто полноценный рабочий юнит. ExecStart — это команда для запуска. WantedBy=multi-user.target означает, что сервис запускается после перехода сервера в многопользовательский режим, что обеспечивает его автозагрузку при старте системы. Всё остальное — это уточнения.
Три секции и их назначение
Каждый unit-файл разделен на секции, заключенные в квадратные скобки. Для службы используются три из них.
[Unit] описывает службу и её связи. Две строки, которые вы будете использовать чаще всего:
[Unit]
Description=My application
After=network-online.target
Wants=network-online.targetDescription — это человекочитаемое описание, которое вы видите в systemctl status. After=network-online.target указывает systemd не запускать программу до тех пор, пока сеть не будет готова; это важно для любого сервиса, который занимает порт или устанавливает исходящие соединения.
[Service] определяет, как выполняется программа. Здесь указывается большинство настроек:
[Service]
ExecStart=/usr/local/bin/myapp --config /etc/myapp/config.toml
WorkingDirectory=/opt/myapp
User=myapp
Restart=on-failure
RestartSec=5
Environment=LOG_LEVEL=infoUser=myapp запускает программу от имени непривилегированной учетной записи вместо root, что является важнейшей мерой безопасности. Здесь нет строки Type=, поэтому systemd использует значение по умолчанию simple и предполагает, что процесс ExecStart остается в foreground; если программа переходит в фоновый режим (fork), ей требуется соответствующий параметр Type=, иначе юнит будет отображаться как активный, хотя сам демон уже завершил работу. Restart=on-failure и RestartSec=5 вынесены в отдельный раздел ниже, так как именно ради них чаще всего и создаются службы.
[Install] определяет, что происходит при включении службы:
[Install]
WantedBy=multi-user.targetWantedBy=multi-user.target — это то, что связывает службу с процессом загрузки при выполнении systemctl enable. Без секции [Install] службу можно запустить вручную, но она не будет автоматически стартовать после перезагрузки.
Включение и мониторинг
После создания или редактирования файла юнита необходимо перезагрузить systemd, чтобы он считал изменения, а затем включить и запустить сервис одной командой:
sudo systemctl daemon-reload
sudo systemctl enable --now myapp.servicedaemon-reload — это шаг, о котором часто забывают: systemd кэширует файлы юнитов, поэтому правки не вступят в силу без перезагрузки. enable --now одновременно включает сервис для автозагрузки и запускает его немедленно. Проверьте статус:
sudo systemctl status myapp.service* myapp.service - My application
Loaded: loaded (/etc/systemd/system/myapp.service; enabled)
Active: active (running) since Wed 2026-07-15 22:40:11 UTC; 3s ago
Main PID: 4123 (myapp)Active: active (running) и enabled — это то, что вам нужно увидеть. Чтобы прочитать вывод программы, запросите журнал только для этого юнита:
sudo journalctl -u myapp.service -fФлаг -f выводит новые строки по мере их появления, подобно tail -f. Всё, что программа записывает в стандартный вывод или стандартный поток ошибок, попадает сюда без какой-либо дополнительной настройки логирования с вашей стороны.
Перезапуск при сбое — основная причина использования сервисов
Главное преимущество сервиса заключается в том, что systemd перезапускает программу, если она завершается аварийно. Для этого достаточно двух строк:
[Service]
Restart=on-failure
RestartSec=5Restart=on-failure перезапускает программу, если она завершается с ненулевым кодом или падает из-за сигнала аварийного завершения, такого как SIGKILL или SIGSEGV. Корректное завершение или остановка по сигналам SIGTERM, SIGINT, SIGHUP или SIGPIPE не вызывают перезапуск. RestartSec=5 задает паузу в пять секунд между попытками, чтобы программа, которая падает мгновенно, не создавала бесконечный цикл перезапусков. Проверьте это, завершив процесс и наблюдая, как systemd восстанавливает его работу. Используйте SIGKILL: стандартный сигнал SIGTERM считается корректной остановкой, поэтому on-failure не приведет к перезапуску сервиса:
sudo systemctl kill -s SIGKILL myapp.service
sudo systemctl status myapp.serviceВ течение пяти секунд статус покажет новый Main PID, и сервис снова перейдет в состояние active (running). Это и есть вся функциональность, и именно поэтому сервис лучше, чем запуск программы в tmux или screen.
Запуск от имени непривилегированного пользователя и усиление безопасности
Если программа будет скомпрометирована, сервис, работающий от имени root, получит полный контроль над сервером. Запускайте сервис от имени отдельного пользователя и используйте директивы systemd для его изоляции. Сначала создайте системную учетную запись без возможности входа в систему и без домашнего каталога:
sudo useradd --system --no-create-home --shell /usr/sbin/nologin myappЗатем настройте User=myapp и добавьте параметры усиления безопасности в [Service]:
[Service]
User=myapp
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=trueКаждая строка ограничивает возможности, которые не требуются программе. NoNewPrivileges=true запрещает процессу получать новые привилегии, даже через setuid-бинарные файлы. PrivateTmp=true предоставляет процессу изолированное пространство /tmp, недоступное другим процессам. ProtectSystem=strict переводит всю файловую систему в режим «только для чтения», за исключением путей, указанных в ReadWritePaths=. ProtectHome=true полностью скрывает /home от процесса. Это тот же принцип минимальных привилегий, что и размещение сервиса за межсетевым экраном: предоставляйте только то, что необходимо. Если вы читали руководство по устранению уязвимостей IPv6-брандмауэра на VPS, то это локальная часть той же концепции. Для сервиса, доступного из Интернета, дополните эту защиту использованием Fail2ban перед SSH и политикой межсетевого экрана по умолчанию «запретить всё».
Чтобы не вводить всё вручную и не допустить ошибок в директивах, сгенерируйте готовый защищенный юнит и скопируйте его:
Таймеры: современная замена cron
Таймер systemd запускает сервис по расписанию и является современной заменой заданий cron. Таймер состоит из двух файлов: .service, который выполняет работу, и .timer, который определяет время запуска. Допустим, вам нужно выполнять резервное копирование ежедневно в 3 часа ночи. Сервис выполняет задачу один раз и завершается:
# /etc/systemd/system/backup.service
[Unit]
Description=Nightly backup
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.shType=oneshot сообщает systemd, что программа запускается, завершает работу и прекращает выполнение, а не остается резидентной в памяти. Таймер задает расписание:
# /etc/systemd/system/backup.timer
[Unit]
Description=Run the nightly backup
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
[Install]
WantedBy=timers.targetOnCalendar=*-*-* 03:00:00 означает 3 часа ночи ежедневно. Проверяйте любое календарное выражение с помощью systemd-analyze calendar "*-*-* 03:00:00": команда подтвердит корректность синтаксиса и выведет время следующего запуска. Persistent=true запускает пропущенное задание сразу после включения сервера, если он был выключен в 3 часа ночи, чего cron делать не умеет. Обратите внимание, что таймер активируется через timers.target, а не multi-user.target. Включайте именно таймер, а не сервис:
sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
systemctl list-timerslist-timers отображает все таймеры с указанием времени их последнего и следующего запуска, что позволяет сразу увидеть, когда сработает ваше задание. Генератор выше создает пару из .service и .timer автоматически при включении режима таймера. В отличие от строки в cron, таймер предоставляет полноценные логи в journal, те же директивы безопасности, что и любой другой сервис, а также возможность догнать пропущенные запуски благодаря Persistent=true. Cron по-прежнему подходит для простых задач, но таймер — более эффективный инструмент, если выполнение задания критически важно.
FAQ
В чем разница между systemd service и cron job?
Сервис поддерживает работу долгоживущей программы: он запускается при загрузке системы, перезапускается при сбоях и записывает логи в journal. Задача cron выполняет короткую команду по расписанию и завершается. Если вам нужно расписание, но при этом требуются логи journal, усиление безопасности и выполнение пропущенных задач, используйте systemd timer. Он связывает расписание .timer с сервисом oneshot и заменяет cron для большинства серверных задач.
Где размещать файл systemd service?
Размещайте собственные юниты в /etc/systemd/system/, называя их с расширением .service. Этот каталог предназначен для юнитов, созданных администратором, и имеет приоритет над юнитами, поставляемыми в пакетах в /lib/systemd/system/. После создания или редактирования файла выполните sudo systemctl daemon-reload, чтобы systemd применил изменения.
Как настроить перезапуск сервиса при сбое?
Добавьте Restart=on-failure и RestartSec=5 в секцию [Service], затем выполните sudo systemctl daemon-reload и перезапустите сервис. systemd перезапустит программу, если она завершится с ненулевым кодом или будет остановлена сигналом аварийного завершения, выдерживая паузу в пять секунд между попытками. Протестируйте это с помощью sudo systemctl kill -s SIGKILL myapp.service: сигнал SIGTERM, используемый по умолчанию, считается штатной остановкой и не вызывает on-failure. Следите за тем, как systemctl status показывает новый PID через несколько секунд.
Как запустить systemd service от имени пользователя, не являющегося root?
Создайте системную учетную запись с помощью sudo useradd --system --no-create-home --shell /usr/sbin/nologin myapp, затем добавьте User=myapp в секцию [Service]. Добавьте NoNewPrivileges=true, PrivateTmp=true и ProtectSystem=strict, чтобы процесс работал с минимально необходимыми правами доступа. Запуск от имени непривилегированного пользователя — это самое важное изменение для повышения безопасности сервиса.
Почему мой сервис не запускается?
Выполните systemctl status myapp.service для получения сводки и journalctl -u myapp.service для просмотра полного вывода. Самые частые причины — неверный путь в ExecStart, отсутствие WorkingDirectory, ошибка прав доступа, из-за которой User= не может прочитать файл, или забытая команда sudo systemctl daemon-reload после внесения правок. В journal отображается сообщение об ошибке самой программы, которое обычно прямо указывает на проблему.