systemd — tworzenie i zarządzanie serwisami
Opublikowano: 9 kwietnia 2026 · Kategoria: VPS / Linux
systemd to system inicjalizacji i menedżer serwisów obecny w każdej nowoczesnej dystrybucji Linux (Ubuntu, Debian, CentOS, Fedora). Gdy wdrażasz aplikację na VPS — Node.js, Python, Go, Java — musisz upewnić się że uruchamia się automatycznie po restarcie serwera, restartuje się po awarii i ma dostęp do odpowiednich zmiennych środowiskowych. To właśnie robi systemd unit file.
Struktura pliku .service
Plik unit serwisu składa się z trzech sekcji: [Unit] (metadane i zależności),
[Service] (jak uruchomić proces) i [Install] (kiedy włączyć przy starcie
systemu). Pliki systemowe leżą w /etc/systemd/system/.
# Minimalna struktura pliku .service # /etc/systemd/system/moja-aplikacja.service [Unit] Description=Moja aplikacja webowa After=network.target [Service] ExecStart=/usr/bin/node /opt/app/index.js Restart=on-failure [Install] WantedBy=multi-user.target
Sekcja [Unit] — metadane i zależności
Sekcja [Unit] definiuje opis serwisu i jego relacje z innymi jednostkami systemd:
- Description= — czytelny opis wyświetlany przez
systemctl status. - After=network.target — serwis startuje dopiero po zainicjalizowaniu sieci.
Dla bazy danych:
After=postgresql.service. - Requires= — twarda zależność: jeśli wymagany serwis nie wystartuje, ten też się nie uruchomi. Używaj ostrożnie.
- Wants= — miękka zależność: systemd spróbuje uruchomić wymieniony serwis, ale niepowodzenie go nie blokuje.
- BindsTo= — serwis jest ściśle powiązany: jeśli powiązany serwis się zatrzyma, ten też się zatrzyma.
Sekcja [Service] — typ i uruchomienie
Type= — rodzaj procesu
| Type | Kiedy używać | Opis |
|---|---|---|
simple | Node.js, Python, większość aplikacji | ExecStart = główny proces. systemd nie czeka na gotowość |
forking | Tradycyjne daemony (nginx, Apache) | Aplikacja forkuje się, rodzic kończy. Wymaga PIDFile= |
notify | Aplikacje z biblioteką libsystemd | Aplikacja wysyła sd_notify("READY=1") gdy jest gotowa |
oneshot | Skrypty jednorazowe, migration runner | Proces się kończy — systemd czeka na zakończenie |
idle | Rzadko używany | Uruchomienie odkładane do braku aktywnych zadań |
ExecStart, ExecStop, ExecReload
ExecStart= to komenda uruchamiająca serwis — musi być pełna ścieżka bezwzględna
(np. /usr/bin/node, nie samo node). Możesz podać kilka
ExecStartPre= komend do wykonania przed startem (np. migracje bazy):
[Service] # Wykonaj migracje przed startem aplikacji ExecStartPre=/usr/bin/node /opt/app/migrate.js # Właściwy start ExecStart=/usr/bin/node /opt/app/server.js # Graceful reload (np. kill -HUP dla nginx) ExecReload=/bin/kill -HUP $MAINPID # Graceful stop ExecStop=/bin/kill -TERM $MAINPID
Restart policies
- Restart=no — nigdy nie restartuj (domyślnie). Dla jednorazowych skryptów.
- Restart=on-failure — restart tylko przy błędzie (exit code != 0, sygnał, timeout).
Nie restartuje po
systemctl stop. - Restart=on-abnormal — restart po sygnale, timeout, watchdog. Nie po exit code.
- Restart=always — restart zawsze, nawet po ręcznym zatrzymaniu. Ostrożnie!
- RestartSec=5 — czekaj 5 sekund przed restartem (zapobiega loopowi awarii).
- StartLimitBurst=5 i StartLimitIntervalSec=30 — max 5 restartów w ciągu 30 sekund, potem serwis wchodzi w stan failed.
EnvironmentFile — bezpieczne sekrety
Nie wpisuj haseł i kluczy API bezpośrednio do pliku .service — plik jest widoczny
przez systemctl cat dla każdego użytkownika z dostępem do systemd. Zamiast tego użyj
EnvironmentFile:
[Service] # Plik z sekretami — chmod 640, własność root:www-data EnvironmentFile=/etc/myapp/production.env # Można też podać zmienne bezpośrednio (tylko dla niesekretnych) Environment=NODE_ENV=production Environment=PORT=3000 ExecStart=/usr/bin/node /opt/app/server.js
# /etc/myapp/production.env DATABASE_URL=postgresql://user:haslo@localhost/mydb SECRET_KEY=klucz_tajny_xyz REDIS_URL=redis://localhost:6379
# Zabezpiecz plik sekretów sudo chmod 640 /etc/myapp/production.env sudo chown root:www-data /etc/myapp/production.env
Watchdog — monitoring procesu
Watchdog to mechanizm wykrywający zawieszony proces (żywy, ale niereagujący). Aplikacja musi
cyklicznie wysyłać sygnał "WATCHDOG=1" przez sd_notify(). Jeśli nie wyśle w
ciągu WatchdogSec, systemd restartuje serwis:
[Service] Type=notify WatchdogSec=30s # Restart jeśli brak WATCHDOG=1 przez 30s Restart=on-failure
Kompletny przykład — serwis Node.js
# /etc/systemd/system/myapp.service [Unit] Description=Moja aplikacja Node.js Documentation=https://github.com/firma/myapp After=network.target postgresql.service redis.service Wants=postgresql.service redis.service [Service] Type=simple User=www-data Group=www-data WorkingDirectory=/opt/app EnvironmentFile=/etc/myapp/production.env Environment=NODE_ENV=production Environment=PORT=3000 # Migracja bazy przed startem (opcjonalnie) ExecStartPre=/usr/bin/node /opt/app/scripts/migrate.js ExecStart=/usr/bin/node /opt/app/server.js ExecReload=/bin/kill -HUP $MAINPID # Restart tylko przy błędach, z 5s opóźnieniem Restart=on-failure RestartSec=5s StartLimitBurst=5 StartLimitIntervalSec=60s # Limity zasobów LimitNOFILE=65536 MemoryMax=512M # Logowanie przez journald StandardOutput=journal StandardError=journal SyslogIdentifier=myapp [Install] WantedBy=multi-user.target
# Załaduj zmiany i uruchom sudo systemctl daemon-reload sudo systemctl enable myapp # Autostart przy restarcie sudo systemctl start myapp # Uruchom teraz sudo systemctl status myapp # Sprawdź stan
Kompletny przykład — serwis Python (Gunicorn)
# /etc/systemd/system/mydjango.service
[Unit]
Description=Django aplikacja przez Gunicorn
After=network.target
[Service]
Type=notify
User=django
Group=django
WorkingDirectory=/opt/django-app
EnvironmentFile=/etc/django/production.env
ExecStart=/opt/django-app/venv/bin/gunicorn \
--workers 4 \
--bind unix:/run/gunicorn.sock \
myproject.wsgi:application
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=5
Restart=on-failure
[Install]
WantedBy=multi-user.target Zarządzanie serwisami — systemctl
| Komenda | Opis |
|---|---|
systemctl start myapp | Uruchom serwis teraz |
systemctl stop myapp | Zatrzymaj serwis |
systemctl restart myapp | Zatrzymaj i uruchom ponownie |
systemctl reload myapp | Graceful reload (ExecReload) |
systemctl enable myapp | Autostart przy restarcie systemu |
systemctl disable myapp | Wyłącz autostart |
systemctl status myapp | Pokaż stan i ostatnie logi |
systemctl daemon-reload | Przeładuj pliki .service po zmianach |
journalctl -u myapp -f | Śledź logi na żywo |
journalctl -u myapp -n 50 --no-pager | Ostatnie 50 linii logów |