Aplikace spuštěná v terminálu skončí, jakmile zavřete SSH nebo VPS restartujete. Nastavíme ji jako službu systemd: poběží na pozadí, po pádu se sama restartuje, spustí se po startu VPS a její výpis najdete v systémovém logu.
Co budete potřebovat
- VPS u StormHostu s Ubuntu 24.04 nebo Debianem 13
- Připojení přes SSH jako root (návod)
- Aplikaci, kterou umíte spustit z příkazové řádky (například Discord bota v Node.js)
- Asi 20 minut
Proč systemd#
systemd je správce služeb, který je součástí Ubuntu i Debianu. Spouští všechny systémové služby (SSH, Nginx, databáze), takže nic dalšího neinstalujete.
| Způsob | Restart po pádu | Start po restartu VPS | Logy | Poznámka |
|---|---|---|---|---|
Terminál, nohup, & | Ne | Ne | Jen do souboru | Vhodné jen na zkoušku. |
screen, tmux | Ne | Ne | Jen v okně | Praktické pro ruční práci, ne pro provoz. |
| pm2 | Ano | Ano (po nastavení) | Vlastní | Další program navíc, jen pro Node.js. |
| systemd | Ano | Ano | journal | Součást systému, funguje pro jakýkoli jazyk. |
Uživatel pro aplikaci#
Aplikace nemá běžet jako root. Když ji někdo napadne, získá jen práva jejího uživatele. Vytvořte systémového uživatele s domovskou složkou v /opt:
useradd --system --create-home --home-dir /opt/mujbot --shell /usr/sbin/nologin mujbot--system– systémový účet pro službu (nižší číslo uživatele, bez hesla).--create-home --home-dir /opt/mujbot– vytvoří složku/opt/mujbot, která patří uživateli.--shell /usr/sbin/nologin– pod uživatelem se nedá přihlásit přes SSH ani terminál.mujbot– jméno uživatele (a zároveň stejnojmenné skupiny). Zvolte vlastní, v návodu ho pak všude nahraďte.
Node.js LTS#
Repozitář Ubuntu 24.04 obsahuje Node.js 18 a Debian 13 Node.js 20. Obě verze už autoři Node.js nepodporují (18 od 30. 4. 2025, 20 od 30. 4. 2026). Nainstalujte aktuální LTS verzi z oficiálních balíčků na nodejs.org (otevře se v novém okně). V říjnu 2026 je aktivní LTS Node.js 24 (koncem října 2026 se LTS stává Node.js 26).
Nejdřív archiv stáhněte a ověřte:
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 -node-v24.21.0-linux-x64.tar.xz: OKPokračujte, jen když vidíte OK. Při FAILED soubor smažte (rm /tmp/node-v*.tar.xz) a stažení zopakujte. Pak Node.js nainstalujte (ve stejném okně terminálu, proměnná NODE_SOUBOR musí zůstat nastavená):
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| Příkaz | Co dělá |
|---|---|
apt -y install curl xz-utils | Stahovací program a rozbalování formátu .tar.xz. |
NODE_SOUBOR=$(… SHASUMS256.txt | grep -o … | head -n 1) | Ze seznamu kontrolních součtů nejnovějšího vydání řady 24 zjistí název souboru pro Linux x64, například node-v24.. |
curl -fsSLO "…/$NODE_SOUBOR" | Stáhne archiv; -O ho uloží pod původním názvem. |
… | grep " $NODE_SOUBOR\$" | sha256sum -c - | Ověří kontrolní součet staženého souboru proti oficiálnímu seznamu. Chrání před poškozeným nebo podvrženým souborem. |
mkdir -p /opt/nodejs | Složka pro Node.js. |
tar -xJf … -C /opt/nodejs --strip-components=1 | Rozbalí archiv (J = formát xz) do /opt/nodejs bez nejvyšší složky s číslem verze. |
ln -sf /opt/nodejs/bin/… /usr/local/bin/… | Vytvoří odkazy na node, npm a npx ve složce, kde je systém hledá. Cesta /usr/local/bin/node zůstane stejná i po aktualizaci. |
Ověření: node -v vypíše v24.…, npm -v číslo verze npm.
Pro jinou řadu nahraďte ve všech adresách latest-v24.x například latest-v26.x. Aktualizace Node.js = zastavit službu, smazat /opt/nodejs a postup zopakovat.
Proč ne nvm
nvm (nabízí ho stránka stažení na nodejs.org) instaluje Node.js do domovské složky uživatele do cesty s číslem verze. Pro službu systemd je potřeba pevná absolutní cesta k programu, která se po aktualizaci nezmění. Proto tu používáme oficiální archiv s odkazem v /usr/local/bin.
Aplikace#
Nahrajte kód aplikace do
/opt/mujbot/app(přes SFTP, návod, nebogit clone). Soubor.envs tokenem a složkunode_modulesnenahrávejte.Předejte soubory uživateli aplikace:
bash chown -R mujbot:mujbot /opt/mujbotNainstalujte balíčky pod uživatelem aplikace:
bash cd /opt/mujbot/app runuser -u mujbot -- npm ci --omit=devrunuser -u mujbot --– spustí příkaz pod uživatelemmujbot(funguje i bez balíčku sudo).npm ci– nainstaluje přesně verze ze souborupackage-lock.json. Pokud ho projekt nemá, použijtenpm install --omit=dev.--omit=dev– vynechá balíčky potřebné jen pro vývoj.
Vyzkoušejte aplikaci ručně:
runuser -u mujbot -- node index.js. Když se spustí bez chyby, ukončete ji Ctrl+C. (Pokud potřebuje token, ruční test udělejte až po kapitole Tajné údaje.)
Tajné údaje v EnvironmentFile#
Token a hesla nepatří do kódu ani do unit souboru. Uložte je do souboru proměnných, který čte jen root, a systemd je předá aplikaci při startu.
Vytvořte soubor
nano /etc/mujbot.env:/etc/ mujbot.env iniDISCORD_TOKEN=<TOKEN_BOTA> NODE_ENV=production- Každý řádek je
NAZEV=hodnota, bezexporta bez mezer kolem=. - Řádky začínající
#jsou komentáře. - V aplikaci hodnotu přečtete jako
process..env. DISCORD_ TOKEN
- Každý řádek je
Omezte práva:
bash chown root:root /etc/mujbot.env chmod 600 /etc/mujbot.envchmod 600– soubor smí číst a měnit jen root. systemd ho čte ještě před přepnutím na uživatele aplikace, proměnné se tak k aplikaci dostanou, ale soubor nikdo jiný nepřečte.
Unit soubor služby#
Vytvořte soubor nano /etc/:
[Unit]
Description=Můj Discord bot
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| Direktiva | Co dělá | Doporučená hodnota | Kdy změnit |
|---|---|---|---|
Description= | Popis služby ve výpisech. | Název aplikace | – |
After= | Služba startuje až po nastavení sítě. | network-online.target | – |
Wants= | Požádá systém, ať na síť počká (After samo nestačí). | network-online.target | – |
Type=simple | Proces z ExecStart je hlavní proces služby a běží v popředí. | simple | Jen když aplikace sama přechází na pozadí (forking). |
User=, Group= | Pod jakým uživatelem aplikace běží. | uživatel aplikace | Nikdy ne root. |
WorkingDirectory= | Pracovní složka; relativní cesty v aplikaci se počítají od ní. | složka aplikace | Při přesunu aplikace. |
EnvironmentFile= | Soubor s proměnnými prostředí. Když neexistuje, služba nenastartuje (pomlčka před cestou, -/etc/…, ho udělá nepovinným). | /etc/mujbot.env | – |
ExecStart= | Příkaz pro spuštění. Program musí být zadaný absolutní cestou. | /usr/local/bin/node /opt/…/index.js | Podle hlavního souboru aplikace. |
Restart=on-failure | Restart, když proces skončí nenulovým kódem nebo ho ukončí signál (kromě SIGTERM, SIGINT, SIGHUP a SIGPIPE, které systemd bere jako řádné ukončení). Ukončení s kódem 0 restart nespustí. | on-failure | always, pokud se má spustit znovu i po řádném ukončení. |
RestartSec=5 | Pauza 5 sekund před restartem (výchozí je 100 ms). | 5 | Delší, pokud aplikace při pádu zatěžuje cizí API. |
NoNewPrivileges=true | Aplikace ani její podprocesy nezískají vyšší práva (například přes programy s bitem setuid). | true | – |
WantedBy= | Po enable se služba spustí při startu VPS. | multi-user.target | – |
Limit opakovaných startů
systemd ve výchozím stavu nepovolí víc než 5 startů služby za 10 sekund. S RestartSec=5 se tento limit nevyčerpá a padající služba se bude zkoušet spouštět pořád dokola každých 5 sekund. Pokud chcete po sérii pádů skončit, přidejte do části [Unit] například StartLimitIntervalSec= a StartLimitBurst=10 (nejvýš 10 startů za 5 minut).
Spuštění služby#
systemctl daemon-reload
systemctl enable --now mujbot
systemctl status mujbotsystemctl daemon-reload– načte nové a změněné unit soubory. Spouštějte po každé úpravě souboru.service.systemctl enable --now mujbot– zapne spuštění po startu VPS a službu hned spustí.systemctl status mujbot– stav služby a posledních pár řádků logu.
Ve výpisu status sledujte:
● mujbot.service - Můj Discord bot
Loaded: loaded (/etc/systemd/system/mujbot.service; enabled; preset: enabled)
Active: active (running) since …
Main PID: 12345 (node)Loaded: … enabled– služba se spustí po restartu VPS.Active: active (running)– běží.failedznamená, že spadla;activating (auto-restart), že čeká na restart.Main PID– číslo procesu aplikace.
Další příkazy: systemctl restart mujbot (restart), systemctl stop mujbot (zastavení), systemctl disable mujbot (vypnutí spouštění po startu).
Logy#
Vše, co aplikace vypíše (console.log, print, chyby), ukládá systemd do journalu.
| Příkaz | Co ukáže |
|---|---|
journalctl -u mujbot -f | Průběžný výpis jako živá konzole; ukončíte Ctrl+C. |
journalctl -u mujbot -n 100 | Posledních 100 řádků. |
journalctl -u mujbot --since "1 hour ago" | Výpis za poslední hodinu. |
journalctl -u mujbot -b | Výpis od posledního startu VPS. |
journalctl --disk-usage | Kolik místa journal zabírá. |
Journal se promazává sám: ve výchozím stavu zabere nejvýš 10 % velikosti souborového systému, maximálně 4 GB.
Python a Java#
Postup je stejný, liší se jen instalace prostředí a řádek ExecStart.
Python
Python 3 je v systému. Pro aplikaci vytvořte virtuální prostředí, ať se balíčky nemíchají se systémovými:
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.txtpython3 -m venv /opt/mujbot/venv– vytvoří samostatné prostředí Pythonu ve složcevenv./opt/mujbot/venv/bin/pip install -r …– nainstaluje balíčky ze souborurequirements.txtdo tohoto prostředí.
V unit souboru použijte Python z prostředí a vypněte bufferování výstupu (jinak se logy v journalu objevují se zpožděním):
Environment=PYTHONUNBUFFERED=1
ExecStart=/opt/mujbot/venv/bin/python /opt/mujbot/app/bot.pyJava
Nainstalujte běhové prostředí Javy ve verzi, kterou aplikace vyžaduje. Pro Javu 21:
apt -y install openjdk-21-jre-headlessPro Javu 25 nainstalujte místo toho balíček openjdk-25-jre-headless. Oba jsou v repozitáři Ubuntu 24.04 i Debianu 13. Verzi ověříte příkazem java -version.
V unit souboru:
ExecStart=/usr/bin/java -Xms512M -Xmx1G -jar /opt/mujbot/app/aplikace.jar
SuccessExitStatus=143-Xms512M -Xmx1G– počáteční a maximální velikost paměti Javy. Maximum nastavte s rezervou pod volnou RAM VPS.SuccessExitStatus=143– Java při řádném zastavení (signál SIGTERM) končí kódem 143. Bez tohoto řádku by systemd po každémsystemctl stophlásil službu jakofailed.
Aktualizace aplikace#
systemctl stop mujbot
cd /opt/mujbot/app
runuser -u mujbot -- git pull
runuser -u mujbot -- npm ci --omit=dev
systemctl start mujbotgit pull– stáhne novou verzi kódu (pokud aplikaci máte v Gitu; jinak nahrajte soubory přes SFTP a spusťtechown -R mujbot:mujbot /opt/mujbot).npm ci --omit=dev– doinstaluje změněné balíčky.
Pokud se změnily jen soubory kódu, stačí systemctl restart mujbot.
Ověření#
systemctl is-enabled mujbotvypíšeenabled,systemctl is-active mujbotvypíšeactive.- Po restartu VPS (
reboot) aplikace běží bez vašeho zásahu. - Zkouška automatického restartu:
systemctl kill -s SIGKILL mujbot(násilně ukončí proces, jako by spadl). Za 5 sekundsystemctl status mujbotznovu ukážeactive (running)s jinýmMain PID. Obyčejnésystemctl killbez-s SIGKILLposílá SIGTERM, který systemd bere jako řádné ukončení, a sRestart=on-failurese služba nespustí znovu.
Když něco nefunguje#
status=203/EXEC#
Systemd nedokázal spustit program z ExecStart. Cesta k programu neexistuje, není absolutní, nebo soubor nejde spustit. Ověřte cestu (ls -l /usr/local/bin/node) a u skriptů první řádek (#!/usr/bin/env …) a právo spuštění.
status=217/USER#
Uživatel z User= neexistuje. Zkontrolujte jméno (id mujbot), nebo uživatele vytvořte podle kapitoly Uživatel pro aplikaci.
status=200/CHDIR#
Složka z WorkingDirectory= neexistuje nebo do ní uživatel nesmí. Zkontrolujte cestu a vlastníka (ls -ld /opt/mujbot/app).
Failed to load environment files: No such file or directory#
Soubor z EnvironmentFile= neexistuje. Zkontrolujte cestu, nebo před ni přidejte -, pokud je soubor nepovinný.
Start request repeated too quickly. (start-limit-hit)#
Služba padala příliš rychle za sebou a systemd ji přestal spouštět. Příčinu najdete v logu (journalctl -u mujbot -n 50). Po opravě vynulujte počítadlo a spusťte službu:
systemctl reset-failed mujbot
systemctl start mujbotWarning: The unit file … changed on disk. Run 'systemctl daemon-reload' to reload units.#
Upravili jste unit soubor a neprovedli systemctl daemon-reload. Spusťte ho a službu restartujte.
Aplikace hlásí EACCES nebo permission denied#
Uživatel aplikace nesmí číst nebo zapisovat soubory. Typicky po nahrání souborů jako root. Opravte vlastníka: chown -R mujbot:mujbot /opt/mujbot. Port pod 1024 (například 80) běžný uživatel otevřít nesmí; nechte aplikaci poslouchat na vyšším portu a před ni dejte Nginx (návod).
Aplikace nevidí proměnnou z .env#
Systemd soubor .env v adresáři aplikace sám nenačítá. Proměnné dejte do souboru z EnvironmentFile=, nebo je v aplikaci načtěte knihovnou (například dotenv). Po změně souboru proměnných službu restartujte.
Shrnutí
- Aplikace běží pod vlastním uživatelem, ne jako root.
- Unit soubor v
/etc/systemd/system/určuje uživatele, složku, příkaz se absolutní cestou a restart po pádu. - Tajné údaje jsou v souboru z
EnvironmentFile=s právy600. - Po úpravě unit souboru:
systemctl daemon-reload; zapnutí a start:systemctl enable --now. - Logy:
journalctl -u <NAZEV_SLUZBY> -f.