Як запустити програму як systemd сервіс
Навчіться створювати unit-файли для автоматичного запуску програм у Linux. Покрокова інструкція з налаштування перезапуску, таймерів та захисту прав доступу.
Що таке сервіс systemd і навіщо він потрібен
Сервіс systemd — це невеликий текстовий файл, який визначає правила запуску програми: запуск під час завантаження, перезапуск у разі збою та перенаправлення виводу в системний журнал. Це всі завдання. Програма, запущена вручну через SSH-сесію, завершує роботу після виходу з системи або перезавантаження сервера. Програма, оформлена як сервіс systemd, продовжує працювати, оскільки її власником є система, а не ваша оболонка (shell).
systemd — це init-система в Ubuntu, Debian, Fedora та більшості сучасних Linux-серверів. Це перший процес, що запускається, і він контролює всі інші процеси. Створюючи файл сервісу, ви передаєте програму під контроль цього контролера. У цьому посібнику розглянуто мінімально необхідний робочий юніт, три обов'язкові секції кожного юніта, способи активації та перегляду логів, запуск за розкладом за допомогою таймера, а також обмеження прав доступу для забезпечення мінімальних привілеїв виконання.
Найменший робочий сервіс
Файл сервісу зберігається в /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]
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; це найважливіший рядок для забезпечення безпеки. 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 — це потрібні вам результати. Щоб переглянути вивід програми, запитайте journal лише для цього юніта:
sudo journalctl -u myapp.service -f-f відображає нові рядки в реальному часі, як tail -f. Усе, що програма пише у standard output або standard error, потрапляє сюди без додаткового налаштування логування.
Перезапуск у разі помилки: причина, чому ви тут
Головна перевага сервісу полягає в тому, що systemd перезапускає вашу програму після її завершення. Це забезпеслюють два рядки:
[Service]
Restart=on-failure
RestartSec=5Restart=on-failure перезапускає програму, якщо вона завершилася з ненульовим кодом або аварійно через сигнал, як-от SIGKILL або SIGSEGV. Чисте завершення або зупинка через SIGTERM, SIGINT, SIGHUP або SIGPIPE не викликають перезапуск. RestartSec=5 створює затримку у 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. Це принцип мінімальних привілеїв, аналогічний використанню firewall: надавайте сервісу лише те, що йому необхідно. Якщо ви прочитали інструкцію щодо закриття вразливостей IPv6 firewall на VPS, то це локальна частина тієї ж стратегії. Для сервісу, що має вихід в інтернет, поєднайте це посилення захисту з Fail2ban перед SSH та firewall з політикою default-deny.
Замість ручного введення всіх цих параметрів, згенеруйте готовий захищений unit-файл і скопіюйте його:
Timers: modern cron
Таймер systemd запускає службу за розкладом і є сучасною заміною cron. Таймер складається з двох файлів: .service, який виконує роботу, та .timer, який визначає час запуску. Припустимо, вам потрібне резервне копіювання щодня о 3:00. Служба виконує завдання один раз і завершується:
# /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:00. Перевірити будь-який календарний вираз можна за допомогою systemd-analyze calendar "*-*-* 03:00:00"; ця команда підтверджує правильність парсингу та виводить наступні моменти запуску. Persistent=true запускає пропущене завдання одразу після завантаження сервера, якщо він був вимкнений о 3:00 — 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?
Service забезпечує безперервну роботу програми: вона запускається під час завантаження, перезапускається у разі помилки та веде логи в journal. Cron job виконує коротку команду за розкладом і завершується. Якщо потрібен розклад, але також потрібні логи journal, hardening та виконання пропущених запусків, використовуйте 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 перезапускає програму, якщо вона завершується з ненульовим кодом або аварійно завершується через сигнал, чекаючи 5 секунд між спробами. Перевірте це за допомогою 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 містить власні повідомлення про помилки програми, які зазвичай прямо вказують на проблему.