SSD Nodes Learn Hosting plans →
Przewodniki Matt ConnorAutor: Matt Connor

Jak uruchomić własny program jako usługa systemd na VPS

Dowiedz się, jak stworzyć plik jednostki .service, aby zapewnić automatyczny start programu po reboocie, restart po awarii oraz dostęp do logów przez journalctl.

Czym jest usługa systemd i dlaczego warto jej używać

Usługa systemd to niewielki plik tekstowy, który instruuje serwer, w jaki sposób uruchomić program: włączyć go podczas startu systemu, zrestartować w razie awarii oraz przekierować całe wyjście programu do dziennika systemowego. To całe zadanie. Program uruchomiony ręcznie w sesji SSH kończy działanie w momencie wylogowania lub restartu serwera. Program opakowany w usługę systemd działa w sposób ciągły, ponieważ to serwer, a nie powłoka użytkownika, zarządza jego procesem.

systemd to system init używany w Ubuntu, Debian, Fedora oraz większości nowoczesnych serwerów Linux. Jest to pierwszy proces uruchamiany w systemie, który nadzoruje wszystkie pozostałe. Nie zawsze tak było, a lektura w jaki sposób systemd zastąpił wcześniejsze skrypty init jest warta uwagi, gdy już zrozumiesz działanie pliku jednostki. Pisząc plik usługi, przekazujesz swój program pod nadzór tego systemu. Niniejszy przewodnik przedstawia najmniejszą działającą jednostkę, trzy sekcje występujące w każdej jednostce, sposób jej aktywacji i odczytu dzienników, uruchamianie według harmonogramu za pomocą timera oraz ograniczanie uprawnień w celu zapewnienia minimalnego poziomu dostępu.

Najmniejsza działająca usługa

Plik usługi znajduje się w /etc/systemd/system/, kończy się rozszerzeniem .service i wymaga tylko kilku linii. Utwórz go dla programu znajdującego się w /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

To kompletna, działająca jednostka. ExecStart to polecenie, które ma zostać uruchomione. WantedBy=multi-user.target oznacza uruchomienie usługi po osiągnięciu przez serwer normalnego trybu pracy wieloużytkownikowej, co zapewnia jej start podczas rozruchu systemu. Wszystkie pozostałe elementy to jedynie doprecyzowanie konfiguracji.

Trzy sekcje i ich przeznaczenie

Każdy plik jednostki jest podzielony na sekcje ujęte w nawiasy kwadratowe. Usługa wykorzystuje trzy z nich.

[Unit] opisuje usługę oraz jej zależności. Dwie linie, które będą używane najczęściej:

[Unit]
Description=My application
After=network-online.target
Wants=network-online.target

Description to czytelna dla człowieka etykieta widoczna w systemctl status. After=network-online.target instruuje systemd, aby nie uruchamiał programu przed nawiązaniem połączenia sieciowego, co jest istotne dla każdej usługi wiążącej port lub nawiązującej połączenia wychodzące.

[Service] określa sposób uruchomienia programu. Tutaj znajduje się większość ustawień:

[Service]
ExecStart=/usr/local/bin/myapp --config /etc/myapp/config.toml
WorkingDirectory=/opt/myapp
User=myapp
Restart=on-failure
RestartSec=5
Environment=LOG_LEVEL=info

User=myapp uruchamia program jako użytkownik bez uprawnień zamiast jako root, co stanowi najważniejszą linię dla bezpieczeństwa. Brak tutaj linii Type=, więc systemd przyjmuje domyślne simple i zakłada, że proces ExecStart pozostaje na pierwszym planie; program, który przechodzi w tło (fork), wymaga odpowiedniego typu Type= dla sposobu startu, w przeciwnym razie jednostka zgłosi stan aktywny, podczas gdy właściwy demon już nie istnieje. Restart=on-failure oraz RestartSec=5 otrzymały własną sekcję poniżej, ponieważ to właśnie one są powodem, dla którego większość użytkowników tworzy własną usługę.

[Install] określa, co dzieje się podczas włączania usługi:

[Install]
WantedBy=multi-user.target

WantedBy=multi-user.target to sekcja łącząca usługę z procesem bootowania po wykonaniu systemctl enable. Bez sekcji [Install] usługę można uruchomić ręcznie, ale nie wystartuje ona samodzielnie po restarcie systemu.

Uruchomienie i monitorowanie

Po utworzeniu lub edycji pliku jednostki należy przeładować systemd, aby wczytał zmiany, a następnie włączyć i uruchomić usługę w jednym kroku:

sudo systemctl daemon-reload
sudo systemctl enable --now myapp.service

daemon-reload to krok, o którym często się zapomina: systemd buforuje pliki jednostek, więc edycja nie przyniesie efektu bez przeładowania. enable --now jednocześnie włącza usługę przy starcie systemu i uruchamia ją natychmiast. Sprawdź stan:

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) oraz enabled to stany, które powinny zostać wyświetlone. Aby odczytać wyjście programu, należy poprosić dziennik o wpisy dotyczące tylko tej jednostki:

sudo journalctl -u myapp.service -f

Flaga -f śledzi nowe linie w czasie rzeczywistym, podobnie jak tail -f. Wszystko, co program zapisuje na standardowe wyjście lub standardowe wyjście błędów, trafia tutaj bez konieczności dodatkowej konfiguracji logowania.

Restart po awarii, czyli główny cel konfiguracji

Główną zaletą usługi jest to, że systemd automatycznie restartuje program w przypadku jego zakończenia. Odpowiadają za to dwie linie:

[Service]
Restart=on-failure
RestartSec=5

Restart=on-failure restartuje program, gdy ten kończy działanie z kodem błędu innym niż zero lub zostaje przerwany przez sygnał awaryjny, taki jak SIGKILL lub SIGSEGV. Poprawne zakończenie lub zatrzymanie za pomocą sygnałów SIGTERM, SIGINT, SIGHUP lub SIGPIPE nie wywołuje restartu. RestartSec=5 wprowadza pięciosekundową przerwę między próbami, dzięki czemu program, który ulega natychmiastowej awarii, nie wpada w nieskończoną pętlę. Należy to zweryfikować poprzez zabicie procesu i obserwację, jak systemd przywraca go do działania. Należy użyć sygnału SIGKILL: domyślny sygnał SIGTERM jest traktowany jako poprawne zatrzymanie, więc on-failure nie spowodowałoby restartu usługi:

sudo systemctl kill -s SIGKILL myapp.service
sudo systemctl status myapp.service

W ciągu pięciu sekund status pokaże nowy Main PID oraz ponowne active (running). To cała funkcjonalność i właśnie dlatego usługa jest lepszym rozwiązaniem niż pozostawienie programu uruchomionego w tmux lub screen.

Uruchomienie jako użytkownik bez uprawnień i wzmocnienie zabezpieczeń

Usługa działająca z uprawnieniami root może przejąć pełną kontrolę nad serwerem w przypadku wykorzystania luki w oprogramowaniu. Należy uruchamiać ją jako dedykowany użytkownik i zastosować dyrektywy systemd, które ograniczą jej środowisko pracy. Najpierw należy utworzyć konto systemowe bez możliwości logowania i katalogu domowego:

sudo useradd --system --no-create-home --shell /usr/sbin/nologin myapp

Następnie należy ustawić User=myapp i dodać linie wzmacniające zabezpieczenia do [Service]:

[Service]
User=myapp
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true

Każda z tych linii usuwa uprawnienia, których program nie potrzebuje do działania. NoNewPrivileges=true uniemożliwia procesowi uzyskanie nowych uprawnień, nawet w przypadku użycia plików binarnych z ustawionym bitem setuid. PrivateTmp=true zapewnia prywatny /tmp, niewidoczny dla innych procesów. ProtectSystem=strict ustawia cały system plików w trybie tylko do odczytu, z wyjątkiem ścieżek wskazanych za pomocą ReadWritePaths=. ProtectHome=true całkowicie ukrywa /home przed procesem. Jest to ta sama zasada ograniczonego przywileju, co umieszczenie usługi za firewallem: należy nadać tylko te uprawnienia, które są niezbędne. Jeśli zapoznano się z przewodnikiem dotyczącym zamykania luki w firewallu IPv6 na VPS, jest to lokalna odpowiedź na to samo zagadnienie. W przypadku usługi wystawionej na dostęp z Internetu, należy połączyć te zabezpieczenia z Fail2ban przed SSH oraz firewallem z domyślną polityką odrzucania ruchu.

Zamiast wpisywać wszystkie te parametry ręcznie i ryzykować błąd w dyrektywach, należy wygenerować kompletną, zabezpieczoną jednostkę i skopiować jej treść:

Toolsystemd service and timer generator

Timery: nowoczesny odpowiednik cron

Timer systemd uruchamia usługę zgodnie z harmonogramem i stanowi nowoczesny zamiennik dla zadań cron. Timer składa się z dwóch plików: .service, który wykonuje pracę, oraz .timer, który określa czas wykonania. Załóżmy, że kopia zapasowa ma być wykonywana codziennie o 3:00 nad ranem. Usługa wykonuje zadanie jednorazowo i kończy działanie:

# /etc/systemd/system/backup.service
[Unit]
Description=Nightly backup

[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh

Type=oneshot informuje systemd, że program uruchamia się, kończy pracę i wyłącza, zamiast pozostawać w pamięci. Timer planuje to zadanie:

# /etc/systemd/system/backup.timer
[Unit]
Description=Run the nightly backup

[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true

[Install]
WantedBy=timers.target

OnCalendar=*-*-* 03:00:00 oznacza godzinę 3:00 każdego dnia. Każde wyrażenie kalendarzowe można przetestować za pomocą systemd-analyze calendar "*-*-* 03:00:00", co potwierdza poprawność parsowania i wyświetla czas następnego uruchomienia. Domyślnie wyświetlane jest tylko jedno nadchodzące uruchomienie; należy dodać --iterations=5, aby zobaczyć pięć kolejnych. Persistent=true uruchamia pominięte zadanie natychmiast po powrocie serwera do pracy, jeśli był on wyłączony o 3:00, czego cron nie potrafi. Należy pamiętać, że timer włącza się za pomocą timers.target, a nie multi-user.target. Należy włączyć timer, a nie usługę:

sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
systemctl list-timers

list-timers wyświetla wszystkie timery wraz z czasem ich ostatniego i następnego uruchomienia, co pozwala szybko sprawdzić, kiedy zadanie zostanie wykonane ponownie. Powyższy generator tworzy parę .service i .timer automatycznie po włączeniu trybu timera. W porównaniu do wpisu w cron, timer zapewnia rzeczywiste logi w journal, te same dyrektywy zabezpieczające co każda inna usługa oraz funkcję nadrabiania pominiętych uruchomień, którą zapewnia Persistent=true. Cron jest wystarczający do prostych zadań; timer jest lepszym narzędziem, gdy zadanie jest istotne.

FAQ

Jaka jest różnica między usługą systemd a zadaniem cron?

Usługa utrzymuje długo działający program: uruchamia go podczas startu systemu, restartuje w razie awarii i zapisuje dane w dzienniku journal. Zadanie cron wykonuje krótkie polecenie zgodnie z harmonogramem, a następnie kończy działanie. Jeśli wymagane jest harmonogramowanie, a jednocześnie obsługa dzienników journal, mechanizmy zabezpieczające oraz nadrabianie pominiętych uruchomień, należy użyć timera systemd. Łączy on harmonogram .timer z usługą oneshot i w większości zadań serwerowych zastępuje cron.

Gdzie umieścić plik usługi systemd?

Własne jednostki należy umieszczać w /etc/systemd/system/, nadając im nazwę kończącą się na .service. Ten katalog jest przeznaczony dla jednostek dodawanych przez administratora i ma priorytet nad jednostkami dostarczanymi przez pakiety w /lib/systemd/system/. Po utworzeniu lub edycji pliku w tej lokalizacji należy wykonać sudo systemctl daemon-reload, aby systemd uwzględnił zmiany.

Jak skonfigurować restart usługi w przypadku awarii?

Należy dodać Restart=on-failure oraz RestartSec=5 do sekcji [Service], a następnie wykonać sudo systemctl daemon-reload i zrestartować usługę. systemd uruchomi ponownie program, gdy ten zakończy się kodem innym niż zero lub zostanie przerwany sygnałem awarii, odczekując pięć sekund między próbami. Test należy przeprowadzić za pomocą sudo systemctl kill -s SIGKILL myapp.service; sygnał SIGTERM, będący sygnałem domyślnym, jest traktowany jako poprawne zatrzymanie i nie wyzwala on-failure. Należy obserwować systemctl status, aby sprawdzić, czy w ciągu kilku sekund pojawi się nowy PID.

Jak uruchomić usługę systemd jako użytkownik inny niż root?

Należy utworzyć konto systemowe za pomocą sudo useradd --system --no-create-home --shell /usr/sbin/nologin myapp, a następnie dodać User=myapp do sekcji [Service]. Należy dodać NoNewPrivileges=true, PrivateTmp=true oraz ProtectSystem=strict, aby proces działał z minimalnym wymaganym dostępem. Uruchamianie jako użytkownik bez uprawnień to najważniejsza pojedyncza zmiana zwiększająca bezpieczeństwo usługi.

Dlaczego usługa nie uruchomiła się?

Należy wykonać systemctl status myapp.service w celu uzyskania podsumowania oraz journalctl -u myapp.service dla pełnego wyjścia. Najczęstsze przyczyny to błędna ścieżka w ExecStart, brakujący WorkingDirectory, błąd uprawnień wynikający z tego, że User= nie może odczytać pliku, lub pominięcie sudo systemctl daemon-reload po edycji. Dziennik journal zawiera komunikat o błędzie wygenerowany przez sam program, który zazwyczaj bezpośrednio wskazuje przyczynę problemu.