Naar de inhoud
Actuele kortingscode:10% kortingGebruiken bij je bestelling

Applicatie 24/7 op een VPS: systemd-service (Node.js, Python, Java)

Je start een applicatie of bot op je VPS als systemd-service: draaien op de achtergrond, automatische herstart na een crash en na een herstart van de VPS, logs in het journal en geheime gegevens buiten de code.

  • gevorderd
  • ~20 min
  • Bijgewerkt op 1-10-2026

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.

ManierHerstart na een crashStart na een herstart van de VPSLogsOpmerking
Terminal, nohup, &NeeNeeAlleen naar een bestandAlleen geschikt om iets uit te proberen.
screen, tmuxNeeNeeAlleen in het vensterHandig voor handmatig werk, niet voor productie.
pm2JaJa (na instellen)Eigen logsNog een extra programma, alleen voor Node.js.
systemdJaJajournalOnderdeel 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:

bash
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/mujbot aan, 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:

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 -
Uitvoertext
node-v24.21.0-linux-x64.tar.xz: OK

Ga 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):

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
CommandoWat het doet
apt -y install curl xz-utilsEen 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.21.0-linux-x64.tar.xz.
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/nodejsEen map voor Node.js.
tar -xJf … -C /opt/nodejs --strip-components=1Pakt 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#

  1. Upload de code van de applicatie naar /opt/mujbot/app (via SFTP, handleiding, of met git clone). Upload het bestand .env met het token en de map node_modules niet.

  2. Draag de bestanden over aan de gebruiker van de applicatie:

    bash
    chown -R mujbot:mujbot /opt/mujbot
  3. Installeer de pakketten als de gebruiker van de applicatie:

    bash
    cd /opt/mujbot/app
    runuser -u mujbot -- npm ci --omit=dev
    • runuser -u mujbot -- – voert het commando uit als de gebruiker mujbot (werkt ook zonder het pakket sudo).
    • npm ci – installeert precies de versies uit het bestand package-lock.json. Heeft het project dat bestand niet, gebruik dan npm install --omit=dev.
    • --omit=dev – slaat de pakketten over die alleen voor ontwikkeling nodig zijn.
  4. 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.

  1. Maak het bestand aan met nano /etc/mujbot.env:

    /etc/mujbot.envini
    DISCORD_TOKEN=<BOT_TOKEN>
    NODE_ENV=production
    • Elke regel is NAAM=waarde, zonder export en zonder spaties rond =.
    • Regels die met # beginnen zijn commentaar.
    • In de applicatie lees je de waarde als process.env.DISCORD_TOKEN.
  2. Beperk de rechten:

    bash
    chown root:root /etc/mujbot.env
    chmod 600 /etc/mujbot.env
    • chmod 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/systemd/system/mujbot.service:

/etc/systemd/system/mujbot.serviceini
[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
DirectiveWat het doetAanbevolen waardeWanneer wijzigen
Description=De beschrijving van de service in overzichten.De naam van de applicatie–
After=network-online.targetDe service start pas nadat het netwerk is ingesteld.network-online.target–
Wants=network-online.targetVraagt het systeem om op het netwerk te wachten (After alleen is niet genoeg).network-online.target–
Type=simpleHet proces uit ExecStart is het hoofdproces van de service en draait op de voorgrond.simpleAlleen als de applicatie zelf naar de achtergrond gaat (forking).
User=, Group=Onder welke gebruiker de applicatie draait.de gebruiker van de applicatieNooit root.
WorkingDirectory=De werkmap; relatieve paden in de applicatie worden vanaf hier berekend.de map van de applicatieAls 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.jsAfhankelijk van het hoofdbestand van de applicatie.
Restart=on-failureHerstart 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-failurealways, als de service ook na een nette afsluiting opnieuw moet starten.
RestartSec=5Een pauze van 5 seconden vóór de herstart (de standaard is 100 ms).5Langer, als de applicatie bij een crash de API van iemand anders belast.
NoNewPrivileges=trueDe applicatie en haar subprocessen krijgen geen hogere rechten (bijvoorbeeld via programma's met de setuid-bit).true–
WantedBy=multi-user.targetNa 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=300 en StartLimitBurst=10 toe (hoogstens 10 starts in 5 minuten).

De service starten#

bash
systemctl daemon-reload
systemctl enable --now mujbot
systemctl status mujbot
  • systemctl 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:

Uitvoertext
● 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. failed betekent 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.

CommandoWat het toont
journalctl -u mujbot -fDoorlopende uitvoer, zoals een live console; je stopt met Ctrl+C.
journalctl -u mujbot -n 100De laatste 100 regels.
journalctl -u mujbot --since "1 hour ago"De uitvoer van het afgelopen uur.
journalctl -u mujbot -bDe uitvoer sinds de laatste start van de VPS.
journalctl --disk-usageHoeveel 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:

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 – maakt een afzonderlijke Python-omgeving aan in de map venv.
  • /opt/mujbot/venv/bin/pip install -r … – installeert de pakketten uit het bestand requirements.txt in 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):

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

Java

Installeer de runtime-omgeving van Java in de versie die de applicatie vereist. Voor Java 21:

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

Installeer 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:

ini
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 elke systemctl stop als failed melden.

De applicatie bijwerken#

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 – haalt de nieuwe versie van de code op (als je de applicatie in Git hebt staan; upload anders de bestanden via SFTP en voer chown -R mujbot:mujbot /opt/mujbot uit).
  • npm ci --omit=dev – installeert de gewijzigde pakketten.

Als alleen de codebestanden zijn gewijzigd, is systemctl restart mujbot genoeg.

Controle#

  • systemctl is-enabled mujbot toont enabled, systemctl is-active mujbot toont active.
  • 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 toont systemctl status mujbot opnieuw active (running) met een andere Main PID. Een gewone systemctl kill zonder -s SIGKILL stuurt SIGTERM, dat systemd als een nette afsluiting beschouwt, en met Restart=on-failure start 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:

bash
systemctl reset-failed mujbot
systemctl start mujbot

Warning: 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 rechten 600.
  • Na het aanpassen van het unitbestand: systemctl daemon-reload; inschakelen en starten: systemctl enable --now.
  • Logs: journalctl -u <SERVICE_NAME> -f.

Zoeken in de handleidingen