Przejdź do treści
Aktualny kupon:10% zniżkiUżyj w zamówieniu

Aplikacja 24/7 na VPS: usługa systemd (Node.js, Python, Java)

Uruchom aplikację lub bota na VPS jako usługę systemd: działa w tle, restartuje się po awarii i po restarcie VPS, loguje do dziennika i trzyma dane poufne poza kodem.

  • zaawansowany
  • ~20 min
  • Zaktualizowano 9.10.2026

Aplikacja uruchomiona w terminalu zatrzymuje się, gdy tylko zamkniesz SSH lub zrestartujesz VPS. Skonfigurujemy ją jako usługę systemd: działa w tle, sama restartuje się po awarii, startuje przy uruchomieniu VPS, a jej wyjście trafia do dziennika systemowego.

Czego potrzebujesz

  • VPS w StormHost z Ubuntu 24.04 lub Debianem 13
  • Połączenie SSH jako root (poradnik)
  • Aplikacja, którą możesz uruchomić z wiersza poleceń (na przykład bot Discord w Node.js)
  • Około 20 minut

Dlaczego systemd#

systemd to menedżer usług dostarczany zarówno z Ubuntu, jak i z Debianem. Uruchamia wszystkie usługi systemowe (SSH, Nginx, bazy danych), więc nie trzeba niczego dodatkowo instalować.

MetodaRestart po awariiStart po restarcie VPSLogiUwaga
Terminal, nohup, &NieNieTylko do plikuDobre tylko do szybkiego testu.
screen, tmuxNieNieTylko w oknieWygodne do pracy ręcznej, nie do produkcji.
pm2TakTak (po skonfigurowaniu)WłasneKolejny program, tylko Node.js.
systemdTakTakdziennikCzęść systemu, działa z dowolnym językiem.

Użytkownik dla aplikacji#

Aplikacja nie powinna działać jako root. Jeśli ktoś ją przejmie, dostanie tylko uprawnienia jej użytkownika. Utwórz użytkownika systemowego z katalogiem domowym w /opt:

bash
useradd --system --create-home --home-dir /opt/mujbot --shell /usr/sbin/nologin mujbot
  • --system – konto systemowe dla usługi (niższe ID użytkownika, bez hasła).
  • --create-home --home-dir /opt/mujbot – tworzy katalog /opt/mujbot należący do użytkownika.
  • --shell /usr/sbin/nologin – nikt nie może zalogować się jako ten użytkownik przez SSH ani w terminalu.
  • mujbot – nazwa użytkownika (i pasującej grupy). Wybierz własną i zamień ją wszędzie w tym poradniku.

Node.js LTS#

Repozytorium Ubuntu 24.04 dostarcza Node.js 18, a Debian 13 Node.js 20. Żadna z tych wersji nie jest już wspierana przez projekt Node.js (18 od 30 kwietnia 2025, 20 od 30 kwietnia 2026). Zainstaluj aktualne wydanie LTS z oficjalnych pakietów na nodejs.org (otwiera się w nowym oknie). W październiku 2026 aktywnym LTS jest Node.js 24 (pod koniec października 2026 LTS zostaje Node.js 26).

Najpierw pobierz archiwum i je zweryfikuj:

bash
apt -y install curl xz-utils
cd /tmp
NODE_SOUBOR=$(curl -fsSL https://nodejs.org/dist/latest-v24.x/SHASUMS256.txt | grep -o 'node-v[0-9.]*-linux-x64.tar.xz' | head -n 1)
curl -fsSLO "https://nodejs.org/dist/latest-v24.x/$NODE_SOUBOR"
curl -fsSL https://nodejs.org/dist/latest-v24.x/SHASUMS256.txt | grep " $NODE_SOUBOR\$" | sha256sum -c -
Wyniktext
node-v24.21.0-linux-x64.tar.xz: OK

Kontynuuj tylko wtedy, gdy widzisz OK. Jeśli dostaniesz FAILED, usuń plik (rm /tmp/node-v*.tar.xz) i pobierz go ponownie. Następnie zainstaluj Node.js (w tym samym oknie terminala, zmienna NODE_SOUBOR musi być nadal ustawiona):

bash
mkdir -p /opt/nodejs
tar -xJf "$NODE_SOUBOR" -C /opt/nodejs --strip-components=1
ln -sf /opt/nodejs/bin/node /usr/local/bin/node
ln -sf /opt/nodejs/bin/npm /usr/local/bin/npm
ln -sf /opt/nodejs/bin/npx /usr/local/bin/npx
PolecenieCo robi
apt -y install curl xz-utilsNarzędzie do pobierania i obsługa rozpakowywania archiwów .tar.xz.
NODE_SOUBOR=$(… SHASUMS256.txt | grep -o … | head -n 1)Czyta listę sum kontrolnych najnowszego wydania 24.x i znajduje nazwę pliku Linux x64, na przykład node-v24.21.0-linux-x64.tar.xz.
curl -fsSLO "…/$NODE_SOUBOR"Pobiera archiwum; -O zapisuje je pod oryginalną nazwą.
… | grep " $NODE_SOUBOR\$" | sha256sum -c -Sprawdza sumę kontrolną pobranego pliku względem oficjalnej listy. Chroni przed uszkodzonym lub podmienionym plikiem.
mkdir -p /opt/nodejsKatalog dla Node.js.
tar -xJf … -C /opt/nodejs --strip-components=1Rozpakowuje archiwum (J = format xz) do /opt/nodejs bez katalogu najwyższego poziomu nazwanego od wersji.
ln -sf /opt/nodejs/bin/… /usr/local/bin/…Tworzy dowiązania do node, npm i npx w katalogu, w którym system szuka programów. Ścieżka /usr/local/bin/node pozostaje taka sama po aktualizacjach.

Kontrola: node -v wypisuje v24.…, npm -v wypisuje wersję npm.

Dla innej linii wydań zamień latest-v24.x we wszystkich URL na przykład na latest-v26.x. Aktualizacja Node.js = zatrzymanie usługi, usunięcie /opt/nodejs i powtórzenie kroków.

Dlaczego nie nvm

nvm (oferowany na stronie pobierania nodejs.org) instaluje Node.js w katalogu domowym użytkownika pod ścieżką zawierającą numer wersji. Usługa systemd potrzebuje stałej ścieżki bezwzględnej do programu, która nie zmienia się po aktualizacji. Dlatego używamy oficjalnego archiwum z dowiązaniem w /usr/local/bin.

Aplikacja#

  1. Prześlij kod aplikacji do /opt/mujbot/app (przez SFTP, poradnik, albo przez git clone). Nie przesyłaj pliku .env z tokenem ani katalogu node_modules.

  2. Przekaż pliki użytkownikowi aplikacji:

    bash
    chown -R mujbot:mujbot /opt/mujbot
  3. Zainstaluj pakiety jako użytkownik aplikacji:

    bash
    cd /opt/mujbot/app
    runuser -u mujbot -- npm ci --omit=dev
    • runuser -u mujbot -- – wykonuje polecenie jako użytkownik mujbot (działa nawet bez pakietu sudo).
    • npm ci – instaluje dokładnie wersje z package-lock.json. Jeśli projekt go nie ma, użyj npm install --omit=dev.
    • --omit=dev – pomija pakiety potrzebne tylko do rozwoju.
  4. Wypróbuj aplikację ręcznie: runuser -u mujbot -- node index.js. Jeśli startuje bez błędów, zatrzymaj ją przez Ctrl+C. (Jeśli potrzebuje tokenu, zrób test ręczny po sekcji Dane poufne.)

Dane poufne w EnvironmentFile#

Tokeny i hasła nie należą do kodu ani do pliku unit. Przechowuj je w pliku zmiennych, który może czytać tylko root, a systemd przekaże je aplikacji przy starcie.

  1. Utwórz plik przez nano /etc/mujbot.env:

    /etc/mujbot.envini
    DISCORD_TOKEN=<TOKEN_BOTA>
    NODE_ENV=production
    • Każda linia to NAZWA=wartość, bez export i bez spacji wokół =.
    • Linie zaczynające się od # to komentarze.
    • W aplikacji odczytujesz wartość jako process.env.DISCORD_TOKEN.
  2. Ogranicz uprawnienia:

    bash
    chown root:root /etc/mujbot.env
    chmod 600 /etc/mujbot.env
    • chmod 600 – plik może czytać i zmieniać tylko root. systemd czyta go przed przełączeniem na użytkownika aplikacji, więc zmienne docierają do aplikacji, ale nikt inny nie może czytać pliku.

Plik unit usługi#

Utwórz plik przez nano /etc/systemd/system/mujbot.service:

/etc/systemd/system/mujbot.serviceini
[Unit]
Description=Moj bot Discord
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=mujbot
Group=mujbot
WorkingDirectory=/opt/mujbot/app
EnvironmentFile=/etc/mujbot.env
ExecStart=/usr/local/bin/node /opt/mujbot/app/index.js
Restart=on-failure
RestartSec=5
NoNewPrivileges=true

[Install]
WantedBy=multi-user.target
DyrektywaCo robiZalecana wartośćKiedy zmieniać
Description=Opis usługi pokazywany na listach.Nazwa aplikacji–
After=network-online.targetUsługa startuje dopiero po uruchomieniu sieci.network-online.target–
Wants=network-online.targetProsi system o poczekanie na sieć (samo After nie wystarcza).network-online.target–
Type=simpleProces z ExecStart jest głównym procesem usługi i działa na pierwszym planie.simpleTylko jeśli aplikacja sama przechodzi w tło (forking).
User=, Group=Jako który użytkownik działa aplikacja.użytkownik aplikacjiNigdy root.
WorkingDirectory=Katalog roboczy; ścieżki względne w aplikacji są liczone od niego.katalog aplikacjiGdy przenosisz aplikację.
EnvironmentFile=Plik ze zmiennymi środowiskowymi. Jeśli nie istnieje, usługa nie wystartuje (myślnik przed ścieżką, -/etc/…, czyni go opcjonalnym)./etc/mujbot.env–
ExecStart=Polecenie startowe. Program musi być podany ścieżką bezwzględną./usr/local/bin/node /opt/…/index.jsZależnie od pliku głównego aplikacji.
Restart=on-failureRestartuje, gdy proces kończy się kodem niezerowym albo zostaje zabity sygnałem (poza SIGTERM, SIGINT, SIGHUP i SIGPIPE, które systemd traktuje jako czyste zakończenie). Zakończenie kodem 0 nie wywołuje restartu.on-failurealways, jeśli ma startować ponownie nawet po czystym zakończeniu.
RestartSec=5Czeka 5 sekund przed restartem (domyślnie 100 ms).5Dłużej, jeśli zawieszająca się aplikacja zalewa zewnętrzne API.
NoNewPrivileges=trueAni aplikacja, ani jej procesy potomne nie mogą zdobyć wyższych uprawnień (na przykład przez programy setuid).true–
WantedBy=multi-user.targetPo enable usługa startuje przy uruchomieniu VPS.multi-user.target–

Limit częstotliwości startów

Domyślnie systemd pozwala na najwyżej 5 startów usługi w ciągu 10 sekund. Przy RestartSec=5 ten limit nigdy nie zostaje osiągnięty, więc zawieszająca się usługa będzie próbować startu co 5 sekund w nieskończoność. Jeśli ma się poddać po serii awarii, dodaj do sekcji [Unit] na przykład StartLimitIntervalSec=300 i StartLimitBurst=10 (najwyżej 10 startów w 5 minut).

Uruchomienie usługi#

bash
systemctl daemon-reload
systemctl enable --now mujbot
systemctl status mujbot
  • systemctl daemon-reload – wczytuje nowe i zmienione pliki unit. Uruchamiaj po każdej zmianie pliku .service.
  • systemctl enable --now mujbot – włącza start przy uruchomieniu VPS i od razu uruchamia usługę.
  • systemctl status mujbot – status usługi i kilka ostatnich linii logu.

W wyjściu status szukaj:

Wyniktext
● mujbot.service - Moj bot Discord
     Loaded: loaded (/etc/systemd/system/mujbot.service; enabled; preset: enabled)
     Active: active (running) since …
   Main PID: 12345 (node)
  • Loaded: … enabled – usługa wystartuje po restarcie VPS.
  • Active: active (running) – działa. failed oznacza awarię; activating (auto-restart) oznacza oczekiwanie na restart.
  • Main PID – ID procesu aplikacji.

Inne polecenia: systemctl restart mujbot (restart), systemctl stop mujbot (zatrzymanie), systemctl disable mujbot (nie startuj przy uruchomieniu).

Logi#

Wszystko, co aplikacja wypisuje (console.log, print, błędy), systemd zapisuje w dzienniku.

PolecenieCo pokazuje
journalctl -u mujbot -fWyjście na żywo jak w konsoli; zatrzymaj przez Ctrl+C.
journalctl -u mujbot -n 100Ostatnie 100 linii.
journalctl -u mujbot --since "1 hour ago"Wyjście z ostatniej godziny.
journalctl -u mujbot -bWyjście od ostatniego startu VPS.
journalctl --disk-usageIle miejsca zajmuje dziennik.

Dziennik sprząta się sam: domyślnie zajmuje najwyżej 10 % rozmiaru systemu plików, do 4 GB.

Python i Java#

Kroki są takie same; różni się tylko instalacja środowiska uruchomieniowego i linia ExecStart.

Python

Python 3 jest już w systemie. Utwórz dla aplikacji środowisko wirtualne, aby jej pakiety nie mieszały się z systemowymi:

bash
apt -y install python3-venv
runuser -u mujbot -- python3 -m venv /opt/mujbot/venv
runuser -u mujbot -- /opt/mujbot/venv/bin/pip install -r /opt/mujbot/app/requirements.txt
  • python3 -m venv /opt/mujbot/venv – tworzy osobne środowisko Pythona w katalogu venv.
  • /opt/mujbot/venv/bin/pip install -r … – instaluje pakiety z requirements.txt do tego środowiska.

W pliku unit użyj Pythona ze środowiska i wyłącz buforowanie wyjścia (inaczej logi pojawiają się w dzienniku z opóźnieniem):

ini
Environment=PYTHONUNBUFFERED=1
ExecStart=/opt/mujbot/venv/bin/python /opt/mujbot/app/bot.py

Java

Zainstaluj środowisko Javy w wersji wymaganej przez aplikację. Dla Javy 21:

bash
apt -y install openjdk-21-jre-headless

Dla Javy 25 zainstaluj zamiast tego pakiet openjdk-25-jre-headless. Oba są w repozytoriach Ubuntu 24.04 i Debiana 13. Wersję sprawdź przez java -version.

W pliku unit:

ini
ExecStart=/usr/bin/java -Xms512M -Xmx1G -jar /opt/mujbot/app/aplikace.jar
SuccessExitStatus=143
  • -Xms512M -Xmx1G – początkowa i maksymalna pamięć Javy. Maksimum ustaw wyraźnie poniżej wolnej RAM VPS.
  • SuccessExitStatus=143 – przy czystym zatrzymaniu (sygnał SIGTERM) Java kończy się kodem 143. Bez tej linii systemd zgłaszałby usługę jako failed po każdym systemctl stop.

Aktualizacja aplikacji#

bash
systemctl stop mujbot
cd /opt/mujbot/app
runuser -u mujbot -- git pull
runuser -u mujbot -- npm ci --omit=dev
systemctl start mujbot
  • git pull – pobiera nową wersję kodu (jeśli aplikacja jest w Git; w przeciwnym razie prześlij pliki przez SFTP i uruchom chown -R mujbot:mujbot /opt/mujbot).
  • npm ci --omit=dev – instaluje zmienione pakiety.

Jeśli zmieniły się tylko pliki kodu, wystarczy systemctl restart mujbot.

Weryfikacja#

  • systemctl is-enabled mujbot wypisuje enabled, systemctl is-active mujbot wypisuje active.
  • Po restarcie VPS (reboot) aplikacja działa bez Twojego udziału.
  • Test automatycznego restartu: systemctl kill -s SIGKILL mujbot (zabija proces siłą, jakby się zawiesił). W ciągu 5 sekund systemctl status mujbot znów pokazuje active (running) z innym Main PID. Samo systemctl kill bez -s SIGKILL wysyła SIGTERM, który systemd traktuje jako czyste zakończenie, więc przy Restart=on-failure usługa nie wystartuje ponownie.

Rozwiązywanie problemów#

status=203/EXEC#

systemd nie mógł uruchomić programu z ExecStart. Ścieżka programu nie istnieje, nie jest bezwzględna albo plik nie jest wykonywalny. Sprawdź ścieżkę (ls -l /usr/local/bin/node), a przy skryptach pierwszą linię (#!/usr/bin/env …) i uprawnienie do wykonywania.

status=217/USER#

Użytkownik z User= nie istnieje. Sprawdź nazwę (id mujbot) albo utwórz użytkownika zgodnie z sekcją Użytkownik dla aplikacji.

status=200/CHDIR#

Katalog z WorkingDirectory= nie istnieje albo użytkownik nie ma do niego dostępu. Sprawdź ścieżkę i właściciela (ls -ld /opt/mujbot/app).

Failed to load environment files: No such file or directory#

Plik z EnvironmentFile= nie istnieje. Sprawdź ścieżkę albo postaw przed nią -, jeśli plik jest opcjonalny.

Start request repeated too quickly. (start-limit-hit)#

Usługa zawiesiła się zbyt wiele razy z rzędu i systemd przestał ją uruchamiać. Przyczynę znajdziesz w logu (journalctl -u mujbot -n 50). Po naprawie wyzeruj licznik i uruchom usługę:

bash
systemctl reset-failed mujbot
systemctl start mujbot

Warning: The unit file … changed on disk. Run 'systemctl daemon-reload' to reload units.#

Edytowano plik unit bez uruchomienia systemctl daemon-reload. Uruchom je i zrestartuj usługę.

Aplikacja zgłasza EACCES lub permission denied#

Użytkownik aplikacji nie może czytać lub zapisywać plików. Zwykle dzieje się to po przesłaniu plików jako root. Popraw właściciela: chown -R mujbot:mujbot /opt/mujbot. Zwykły użytkownik nie może otworzyć portu poniżej 1024 (na przykład 80); niech aplikacja nasłuchuje na wyższym porcie, a przed nią postaw Nginx (poradnik).

Aplikacja nie widzi zmiennej z .env#

systemd sam nie wczytuje pliku .env z katalogu aplikacji. Umieść zmienne w pliku z EnvironmentFile= albo wczytaj je w aplikacji biblioteką (na przykład dotenv). Po zmianie pliku zmiennych zrestartuj usługę.

Podsumowanie

  • Aplikacja działa jako własny użytkownik, nie jako root.
  • Plik unit w /etc/systemd/system/ ustawia użytkownika, katalog, polecenie ze ścieżką bezwzględną i restart po awarii.
  • Dane poufne są w pliku z EnvironmentFile= z uprawnieniami 600.
  • Po edycji pliku unit: systemctl daemon-reload; włączenie i start: systemctl enable --now.
  • Logi: journalctl -u <NAZWA_USLUGI> -f.

Wyszukiwanie w poradnikach