Een applicatie die je in de terminal start, stopt zodra je SSH sluit of de VPS herstart. We stellen haar in als systemd-service: ze draait op de achtergrond, herstart zichzelf na een crash, start bij het opstarten van de VPS en haar uitvoer vind je in de systeemlog.
Wat je nodig hebt
- Een VPS bij StormHost met Ubuntu 24.04 of Debian 13
- Een SSH-verbinding als root (handleiding)
- Een applicatie die je vanaf de opdrachtregel kunt starten (bijvoorbeeld een Discord-bot in Node.js)
- Ongeveer 20 minuten
Waarom systemd#
systemd is de servicebeheerder die onderdeel is van zowel Ubuntu als Debian. Het start alle systeemservices (SSH, Nginx, databases), dus je hoeft niets extra's te installeren.
| Manier | Herstart na een crash | Start na een herstart van de VPS | Logs | Opmerking |
|---|---|---|---|---|
Terminal, nohup, & | Nee | Nee | Alleen naar een bestand | Alleen geschikt om iets uit te proberen. |
screen, tmux | Nee | Nee | Alleen in het venster | Handig voor handmatig werk, niet voor productie. |
| pm2 | Ja | Ja (na instellen) | Eigen logs | Nog een extra programma, alleen voor Node.js. |
| systemd | Ja | Ja | journal | Onderdeel van het systeem, werkt voor elke taal. |
Een gebruiker voor de applicatie#
Een applicatie hoort niet als root te draaien. Als iemand haar aanvalt, krijgt hij alleen de rechten van haar gebruiker. Maak een systeemgebruiker aan met een thuismap in /opt:
useradd --system --create-home --home-dir /opt/mujbot --shell /usr/sbin/nologin mujbot--system– een systeemaccount voor een service (lager gebruikersnummer, zonder wachtwoord).--create-home --home-dir /opt/mujbot– maakt de map/opt/mujbotaan, die eigendom is van de gebruiker.--shell /usr/sbin/nologin– als deze gebruiker kun je niet inloggen via SSH of de terminal.mujbot– de naam van de gebruiker (en meteen ook van de gelijknamige groep). Kies een eigen naam en vervang hem dan overal in de handleiding.
Node.js LTS#
De repository van Ubuntu 24.04 bevat Node.js 18 en die van Debian 13 Node.js 20. Beide versies worden door de makers van Node.js niet meer ondersteund (18 sinds 30 april 2025, 20 sinds 30 april 2026). Installeer de actuele LTS-versie uit de officiële pakketten op nodejs.org (opent in een nieuw venster). In oktober 2026 is Node.js 24 de actieve LTS (eind oktober 2026 wordt Node.js 26 de LTS).
Download eerst het archief en controleer het:
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: OKGa alleen verder als je OK ziet. Verwijder bij FAILED het bestand (rm /tmp/node-v*.tar.xz) en download het opnieuw. Installeer daarna Node.js (in hetzelfde terminalvenster, de variabele NODE_SOUBOR moet ingesteld blijven):
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| Commando | Wat het doet |
|---|---|
apt -y install curl xz-utils | Een downloadprogramma en het uitpakken van het formaat .tar.xz. |
NODE_SOUBOR=$(… SHASUMS256.txt | grep -o … | head -n 1) | Haalt uit de lijst met controlesommen van de nieuwste release van de 24-reeks de bestandsnaam voor Linux x64, bijvoorbeeld node-v24.. |
curl -fsSLO "…/$NODE_SOUBOR" | Downloadt het archief; -O slaat het op onder de oorspronkelijke naam. |
… | grep " $NODE_SOUBOR\$" | sha256sum -c - | Controleert de controlesom van het gedownloade bestand aan de hand van de officiële lijst. Beschermt tegen een beschadigd of vervalst bestand. |
mkdir -p /opt/nodejs | Een map voor Node.js. |
tar -xJf … -C /opt/nodejs --strip-components=1 | Pakt het archief uit (J = xz-formaat) in /opt/nodejs, zonder de bovenste map met het versienummer. |
ln -sf /opt/nodejs/bin/… /usr/local/bin/… | Maakt koppelingen naar node, npm en npx in een map waar het systeem ze zoekt. Het pad /usr/local/bin/node blijft ook na een update hetzelfde. |
Controle: node -v toont v24.…, npm -v het versienummer van npm.
Vervang voor een andere reeks in alle adressen latest-v24.x door bijvoorbeeld latest-v26.x. Node.js bijwerken = de service stoppen, /opt/nodejs verwijderen en de stappen herhalen.
Waarom niet nvm
nvm (de downloadpagina op nodejs.org biedt het aan) installeert Node.js in de thuismap van de gebruiker, in een pad met het versienummer. Een systemd-service heeft een vast absoluut pad naar het programma nodig, dat na een update niet verandert. Daarom gebruiken we hier het officiële archief met een koppeling in /usr/local/bin.
De applicatie#
Upload de code van de applicatie naar
/opt/mujbot/app(via SFTP, handleiding, of metgit clone). Upload het bestand.envmet het token en de mapnode_modulesniet.Draag de bestanden over aan de gebruiker van de applicatie:
bash chown -R mujbot:mujbot /opt/mujbotInstalleer de pakketten als de gebruiker van de applicatie:
bash cd /opt/mujbot/app runuser -u mujbot -- npm ci --omit=devrunuser -u mujbot --– voert het commando uit als de gebruikermujbot(werkt ook zonder het pakket sudo).npm ci– installeert precies de versies uit het bestandpackage-lock.json. Heeft het project dat bestand niet, gebruik dannpm install --omit=dev.--omit=dev– slaat de pakketten over die alleen voor ontwikkeling nodig zijn.
Test de applicatie handmatig:
runuser -u mujbot -- node index.js. Als ze zonder fouten start, stop je haar met Ctrl+C. (Als ze een token nodig heeft, doe je de handmatige test pas na het hoofdstuk Geheime gegevens.)
Geheime gegevens in EnvironmentFile#
Een token en wachtwoorden horen niet in de code en ook niet in het unitbestand. Sla ze op in een bestand met variabelen dat alleen root kan lezen; systemd geeft ze bij het starten door aan de applicatie.
Maak het bestand aan met
nano /etc/mujbot.env:/etc/ mujbot.env iniDISCORD_TOKEN=<BOT_TOKEN> NODE_ENV=production- Elke regel is
NAAM=waarde, zonderexporten zonder spaties rond=. - Regels die met
#beginnen zijn commentaar. - In de applicatie lees je de waarde als
process..env. DISCORD_ TOKEN
- Elke regel is
Beperk de rechten:
bash chown root:root /etc/mujbot.env chmod 600 /etc/mujbot.envchmod 600– alleen root mag het bestand lezen en wijzigen. systemd leest het nog vóór het overschakelen naar de gebruiker van de applicatie. Zo komen de variabelen bij de applicatie terecht, maar kan niemand anders het bestand lezen.
Het unitbestand van de service#
Maak het bestand aan met nano /etc/:
[Unit]
Description=Mijn 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| Directive | Wat het doet | Aanbevolen waarde | Wanneer wijzigen |
|---|---|---|---|
Description= | De beschrijving van de service in overzichten. | De naam van de applicatie | – |
After= | De service start pas nadat het netwerk is ingesteld. | network-online.target | – |
Wants= | Vraagt het systeem om op het netwerk te wachten (After alleen is niet genoeg). | network-online.target | – |
Type=simple | Het proces uit ExecStart is het hoofdproces van de service en draait op de voorgrond. | simple | Alleen als de applicatie zelf naar de achtergrond gaat (forking). |
User=, Group= | Onder welke gebruiker de applicatie draait. | de gebruiker van de applicatie | Nooit root. |
WorkingDirectory= | De werkmap; relatieve paden in de applicatie worden vanaf hier berekend. | de map van de applicatie | Als je de applicatie verplaatst. |
EnvironmentFile= | Het bestand met omgevingsvariabelen. Als het niet bestaat, start de service niet (een minteken vóór het pad, -/etc/…, maakt het optioneel). | /etc/mujbot.env | – |
ExecStart= | Het commando om te starten. Het programma moet met een absoluut pad zijn opgegeven. | /usr/local/bin/node /opt/…/index.js | Afhankelijk van het hoofdbestand van de applicatie. |
Restart=on-failure | Herstart als het proces eindigt met een code die niet nul is of door een signaal wordt beëindigd (behalve SIGTERM, SIGINT, SIGHUP en SIGPIPE, die systemd als een nette afsluiting beschouwt). Een afsluiting met code 0 leidt niet tot een herstart. | on-failure | always, als de service ook na een nette afsluiting opnieuw moet starten. |
RestartSec=5 | Een pauze van 5 seconden vóór de herstart (de standaard is 100 ms). | 5 | Langer, als de applicatie bij een crash de API van iemand anders belast. |
NoNewPrivileges=true | De applicatie en haar subprocessen krijgen geen hogere rechten (bijvoorbeeld via programma's met de setuid-bit). | true | – |
WantedBy= | Na enable start de service bij het opstarten van de VPS. | multi-user.target | – |
Limiet op herhaalde starts
systemd staat standaard niet meer dan 5 starts van een service binnen 10 seconden toe. Met RestartSec=5 wordt die limiet niet bereikt en blijft een crashende service het elke 5 seconden opnieuw proberen. Wil je dat het na een reeks crashes ophoudt, voeg dan aan het gedeelte [Unit] bijvoorbeeld StartLimitIntervalSec= en StartLimitBurst=10 toe (hoogstens 10 starts in 5 minuten).
De service starten#
systemctl daemon-reload
systemctl enable --now mujbot
systemctl status mujbotsystemctl daemon-reload– leest nieuwe en gewijzigde unitbestanden in. Voer dit uit na elke wijziging van een.service-bestand.systemctl enable --now mujbot– schakelt het starten bij het opstarten van de VPS in en start de service meteen.systemctl status mujbot– de status van de service en de laatste paar regels van de log.
Let in de uitvoer van status op het volgende:
● mujbot.service - Mijn Discord-bot
Loaded: loaded (/etc/systemd/system/mujbot.service; enabled; preset: enabled)
Active: active (running) since …
Main PID: 12345 (node)Loaded: … enabled– de service start na een herstart van de VPS.Active: active (running)– de service draait.failedbetekent dat hij is gecrasht;activating (auto-restart)dat hij op een herstart wacht.Main PID– het procesnummer van de applicatie.
Andere commando's: systemctl restart mujbot (herstarten), systemctl stop mujbot (stoppen), systemctl disable mujbot (het starten bij het opstarten uitschakelen).
Logs#
Alles wat de applicatie uitvoert (console.log, print, fouten), slaat systemd op in het journal.
| Commando | Wat het toont |
|---|---|
journalctl -u mujbot -f | Doorlopende uitvoer, zoals een live console; je stopt met Ctrl+C. |
journalctl -u mujbot -n 100 | De laatste 100 regels. |
journalctl -u mujbot --since "1 hour ago" | De uitvoer van het afgelopen uur. |
journalctl -u mujbot -b | De uitvoer sinds de laatste start van de VPS. |
journalctl --disk-usage | Hoeveel ruimte het journal inneemt. |
Het journal ruimt zichzelf op: standaard neemt het hoogstens 10% van de grootte van het bestandssysteem in, met een maximum van 4 GB.
Python en Java#
De stappen zijn hetzelfde, alleen de installatie van de omgeving en de regel ExecStart verschillen.
Python
Python 3 staat al op het systeem. Maak voor de applicatie een virtuele omgeving aan, zodat de pakketten niet met die van het systeem worden vermengd:
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– maakt een afzonderlijke Python-omgeving aan in de mapvenv./opt/mujbot/venv/bin/pip install -r …– installeert de pakketten uit het bestandrequirements.txtin deze omgeving.
Gebruik in het unitbestand de Python uit de omgeving en schakel het bufferen van de uitvoer uit (anders verschijnen de logs met vertraging in het journal):
Environment=PYTHONUNBUFFERED=1
ExecStart=/opt/mujbot/venv/bin/python /opt/mujbot/app/bot.pyJava
Installeer de runtime-omgeving van Java in de versie die de applicatie vereist. Voor Java 21:
apt -y install openjdk-21-jre-headlessInstalleer voor Java 25 in plaats daarvan het pakket openjdk-25-jre-headless. Beide staan in de repository van zowel Ubuntu 24.04 als Debian 13. De versie controleer je met het commando java -version.
In het unitbestand:
ExecStart=/usr/bin/java -Xms512M -Xmx1G -jar /opt/mujbot/app/aplikace.jar
SuccessExitStatus=143-Xms512M -Xmx1G– de begingrootte en de maximale grootte van het geheugen van Java. Stel het maximum met een marge onder het vrije RAM van de VPS in.SuccessExitStatus=143– Java eindigt bij een nette stop (signaal SIGTERM) met code 143. Zonder deze regel zou systemd de service na elkesystemctl stopalsfailedmelden.
De applicatie bijwerken#
systemctl stop mujbot
cd /opt/mujbot/app
runuser -u mujbot -- git pull
runuser -u mujbot -- npm ci --omit=dev
systemctl start mujbotgit pull– haalt de nieuwe versie van de code op (als je de applicatie in Git hebt staan; upload anders de bestanden via SFTP en voerchown -R mujbot:mujbot /opt/mujbotuit).npm ci --omit=dev– installeert de gewijzigde pakketten.
Als alleen de codebestanden zijn gewijzigd, is systemctl restart mujbot genoeg.
Controle#
systemctl is-enabled mujbottoontenabled,systemctl is-active mujbottoontactive.- Na een herstart van de VPS (
reboot) draait de applicatie zonder dat je iets hoeft te doen. - De automatische herstart testen:
systemctl kill -s SIGKILL mujbot(beëindigt het proces geforceerd, alsof het is gecrasht). Na 5 seconden toontsystemctl status mujbotopnieuwactive (running)met een andereMain PID. Een gewonesystemctl killzonder-s SIGKILLstuurt SIGTERM, dat systemd als een nette afsluiting beschouwt, en metRestart=on-failurestart de service dan niet opnieuw.
Als iets niet werkt#
status=203/EXEC#
systemd kon het programma uit ExecStart niet starten. Het pad naar het programma bestaat niet, is niet absoluut, of het bestand is niet uitvoerbaar. Controleer het pad (ls -l /usr/local/bin/node) en bij scripts de eerste regel (#!/usr/bin/env …) en het uitvoerrecht.
status=217/USER#
De gebruiker uit User= bestaat niet. Controleer de naam (id mujbot), of maak de gebruiker aan volgens het hoofdstuk Een gebruiker voor de applicatie.
status=200/CHDIR#
De map uit WorkingDirectory= bestaat niet of de gebruiker mag er niet in. Controleer het pad en de eigenaar (ls -ld /opt/mujbot/app).
Failed to load environment files: No such file or directory#
Het bestand uit EnvironmentFile= bestaat niet. Controleer het pad, of zet er - voor als het bestand optioneel is.
Start request repeated too quickly. (start-limit-hit)#
De service crashte te snel achter elkaar en systemd is gestopt met het starten ervan. De oorzaak vind je in de log (journalctl -u mujbot -n 50). Zet na het herstellen de teller op nul en start de service:
systemctl reset-failed mujbot
systemctl start mujbotWarning: The unit file … changed on disk. Run 'systemctl daemon-reload' to reload units.#
Je hebt het unitbestand aangepast en systemctl daemon-reload niet uitgevoerd. Voer het uit en herstart de service.
De applicatie meldt EACCES of permission denied#
De gebruiker van de applicatie mag de bestanden niet lezen of schrijven. Dat gebeurt meestal nadat je bestanden als root hebt geüpload. Herstel de eigenaar: chown -R mujbot:mujbot /opt/mujbot. Een poort onder 1024 (bijvoorbeeld 80) mag een gewone gebruiker niet openen; laat de applicatie op een hogere poort luisteren en zet er Nginx voor (handleiding).
De applicatie ziet een variabele uit .env niet#
systemd laadt het bestand .env in de map van de applicatie niet uit zichzelf. Zet de variabelen in het bestand uit EnvironmentFile=, of laad ze in de applicatie met een bibliotheek (bijvoorbeeld dotenv). Herstart de service nadat je het bestand met variabelen hebt gewijzigd.
Samenvatting
- De applicatie draait onder een eigen gebruiker, niet als root.
- Het unitbestand in
/etc/systemd/system/bepaalt de gebruiker, de map, het commando met een absoluut pad en de herstart na een crash. - Geheime gegevens staan in het bestand uit
EnvironmentFile=met de rechten600. - Na het aanpassen van het unitbestand:
systemctl daemon-reload; inschakelen en starten:systemctl enable --now. - Logs:
journalctl -u <SERVICE_NAME> -f.
Meer bronnen#
- systemd.service (man) (opent in een nieuw venster)
- systemd.exec (man) (opent in een nieuw venster)
- systemd.unit (man) (opent in een nieuw venster)
- journalctl (man) (opent in een nieuw venster)
- Node.js: downloads en releases (opent in een nieuw venster)
- Node.js: releaseschema (opent in een nieuw venster)