PM2 + Nginx reverse proxy: een Node.js-app aan poort 80 koppelen

TestomgevingDebian-familieUbuntu 24.04.5 LTSgewoon gebruikersaccount
Werking van de Node.js-app en reverse proxy gecontroleerd

Op een Ubuntu 24.04-server hebben we een Node.js PM2 Nginx reverse proxy opgezet: PM2 voert de app uit en Nginx geeft verzoeken die op poort 80 binnenkomen door aan de app. We hebben ook gecontroleerd dat PM2 de app opnieuw start, zelfs als het app-proces wordt beëindigd of de server opnieuw opstart.

De resultaatvakken hieronder tonen de uitvoer van opdrachten die zijn uitgevoerd met de gewone gebruiker deploy. Op de plek van het domein staat de voorbeeldwaarde example.com, en de verzoeken zijn binnen de server verstuurd met een Host-header.

🎯 Kernpunten
🗂️ Verloop van het verzoek
📂 NginxReverse proxy
📄 CertbotHTTPS-certificaat
📂 PM2App uitvoeren en autostart
📄 Node.js-app
App uitvoeren met PM2 en automatisch starten bij het opstarten
Nginx geeft verzoeken op poort 80 door aan de app

1️⃣ Node.js installeren

Node.js wordt geïnstalleerd door de NodeSource-repository aan apt toe te voegen, en we kozen Node.js 26, de LTS-versie per november 2026.

🔲 NodeSource setup_26.x

export DEBIAN_FRONTEND=noninteractive

Als je deze waarde eerst instelt, verschijnt er tijdens de installatie van pakketten geen venster dat om instellingen vraagt. De optie -E van het sudo-commando hieronder geeft deze waarde ook door aan het commando dat met root-rechten draait.

curl -fsSL https://deb.nodesource.com/setup_26.x | sudo -E bash -
2026-11-07 16:08:16 - Repository configured successfully.
2026-11-07 16:08:16 - To install Node.js, run: apt install nodejs -y

Zodra het script de repository heeft toegevoegd, kun je Node.js net als een Ubuntu-pakket met apt installeren.

🔲 apt-get install nodejs

sudo -E apt-get install -y nodejs
Setting up libatomic1:amd64 (14.2.0-4ubuntu2~24.04.1) ...
Setting up nodejs (26.13.0-1nodesource1) ...
Processing triggers for libc-bin (2.39-0ubuntu8.9) ...
Processing triggers for man-db (2.12.0-4build2) ...

🔲 node -v, npm -v

node -v
v26.13.0
npm -v
11.20.0

npm zit in het pakket nodejs, dus je hoeft het niet apart te installeren.

2️⃣ Node.js-test-app

Om te zien of PM2 en Nginx goed gekoppeld zijn, maak je een test-app die één regel tekst teruggeeft wanneer er een verzoek binnenkomt.

🔲 app.js

mkdir -p ~/hello

Maak onder je thuismap de map hello aan en schrijf daarin het bestand app.js.

cat > ~/hello/app.js <<'APP'
const http = require('http');
http.createServer((req, res) => {
  console.log(req.method, req.url);
  res.end('hello from node\n');
}).listen(3000, '127.0.0.1');
APP

De app accepteert alleen verzoeken op poort 3000 van 127.0.0.1, dus van buiten de server is hij niet direct te openen. Nginx stellen we zo in dat het verzoeken van buiten ontvangt en aan deze poort doorgeeft.

3️⃣ App uitvoeren met PM2 en automatisch starten

PM2 is een procesbeheerder die Node.js-apps op de achtergrond uitvoert en een app opnieuw start wanneer het proces stopt. In dit deel stellen we ook het automatisch starten in, dat de app weer opstart wanneer de server opstart.

🔲 npm install -g pm2

sudo npm install -g pm2
added 45 packages in 5s
pm2 -v
[PM2] Spawning PM2 daemon with pm2_home=/home/deploy/.pm2
[PM2] PM2 Successfully daemonized
7.1.2

Bij het eerste pm2-commando start ook de PM2-daemon, en de geïnstalleerde PM2 is versie 7.1.2.

🔲 pm2 start

cd ~/hello

Ga naar de map met app.js en voer de app daarna uit met PM2.

pm2 start app.js --name hello
[PM2] Starting /home/deploy/hello/app.js in fork_mode (1 instance)
[PM2] Done.
┌────┬──────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id │ name     │ namespace   │ version │ mode    │ pid      │ uptime │ ↺    │ status    │ cpu      │ mem      │ user     │ watching │
├────┼──────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 0  │ hello    │ default     │ N/A     │ fork    │ 17257    │ 0s     │ 0    │ online    │ 0%       │ 26.3mb   │ deploy   │ disabled │
└────┴──────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘

--name is de app-naam die in de PM2-lijst verschijnt, en latere commando's wijzen de app met deze naam aan.

pm2 list
┌────┬──────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id │ name     │ namespace   │ version │ mode    │ pid      │ uptime │ ↺    │ status    │ cpu      │ mem      │ user     │ watching │
├────┼──────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 0  │ hello    │ default     │ N/A     │ fork    │ 17257    │ 2s     │ 0    │ online    │ 0%       │ 58.5mb   │ deploy   │ disabled │
└────┴──────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
host metrics | cpu: 0% | ram usage: 38.8% | disk: ⇓ 0mb/s ⇑ 0mb/s

online in de kolom status betekent dat de app draait, en de kolom ↺ is het aantal keren dat PM2 de app opnieuw heeft gestart.

🔲 curl 127.0.0.1:3000

Als je binnen de server een verzoek verstuurt, komt de regel tekst die de app teruggeeft ongewijzigd terug.

curl -sS http://127.0.0.1:3000/
hello from node

🔲 pm2 restart

Nadat je de app-code hebt aangepast, herstart je de app om de wijziging toe te passen.

pm2 restart hello
Use --update-env to update environment variables
[PM2] Applying action restartProcessId on app [hello](ids: [ 0 ])
[PM2] [hello](0) ✓
┌────┬──────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id │ name     │ namespace   │ version │ mode    │ pid      │ uptime │ ↺    │ status    │ cpu      │ mem      │ user     │ watching │
├────┼──────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 0  │ hello    │ default     │ N/A     │ fork    │ 17306    │ 0s     │ 1    │ online    │ 0%       │ 19.3mb   │ deploy   │ disabled │
└────┴──────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
host metrics | cpu: 0% | ram usage: 38.8% | disk: ⇓ 0mb/s ⇑ 0mb/s
pm2 list
┌────┬──────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id │ name     │ namespace   │ version │ mode    │ pid      │ uptime │ ↺    │ status    │ cpu      │ mem      │ user     │ watching │
├────┼──────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 0  │ hello    │ default     │ N/A     │ fork    │ 17306    │ 2s     │ 1    │ online    │ 0%       │ 58.3mb   │ deploy   │ disabled │
└────┴──────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
host metrics | cpu: 0% | ram usage: 38.8% | disk: ⇓ 0mb/s ⇑ 0mb/s

Na de herstart veranderde de pid van 17257 naar 17306 en ging de kolom ↺ naar 1.

🔲 kill -9

Een plotselinge stop van het app-proces kun je nabootsen met kill -9, en pm2 pid toont het procesnummer van de app.

kill -9 "$(pm2 pid hello)"

Controleer de PM2-lijst na 3 seconden opnieuw.

pm2 list
┌────┬──────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id │ name     │ namespace   │ version │ mode    │ pid      │ uptime │ ↺    │ status    │ cpu      │ mem      │ user     │ watching │
├────┼──────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 0  │ hello    │ default     │ N/A     │ fork    │ 17797    │ 3s     │ 2    │ online    │ 0%       │ 58.6mb   │ deploy   │ disabled │
└────┴──────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
host metrics | cpu: 51.2% | ram usage: 35.8% | eth0: ⇓ 1kb/s ⇑ 1kb/s | eth1: ⇓ 19kb/s ⇑ 0mb/s | disk: ⇓ 728kb/s ⇑ 3.1mb/s

PM2 had de app opnieuw gestart met de nieuwe pid 17797 en hij was online, en de kolom ↺ was naar 2 gegaan.

🔲 pm2 save

pm2 save
[PM2] Saving current process list...
[PM2] Successfully saved in /home/deploy/.pm2/dump.pm2

pm2 save slaat de lijst met draaiende apps op in het bestand dump.pm2, en bij het opstarten start PM2 de apps opnieuw volgens deze lijst.

🔲 pm2 startup

Om PM2 bij het opstarten te laten draaien, registreer je PM2 als systemd-service. Het commando hieronder maakt de service aan met de naam van het huidige account en de thuismap.

sudo env PATH="$PATH" pm2 startup systemd -u "$(id -un)" --hp "$HOME"
ExecStart=/usr/lib/node_modules/pm2/bin/pm2 resurrect
ExecReload=/usr/lib/node_modules/pm2/bin/pm2 reload all
ExecStop=/usr/lib/node_modules/pm2/bin/pm2 kill

[Install]
WantedBy=multi-user.target

Target path
/etc/systemd/system/pm2-deploy.service
Command list
[ 'systemctl enable pm2-deploy' ]
[PM2] Writing init configuration in /etc/systemd/system/pm2-deploy.service
[PM2] Making script booting at startup...
[PM2] [-] Executing: systemctl enable pm2-deploy...
Created symlink /etc/systemd/system/multi-user.target.wants/pm2-deploy.service → /etc/systemd/system/pm2-deploy.service.
[PM2] [v] Command successfully executed.

env PATH is het deel waarmee pm2, uitgevoerd met root-rechten, het node-pad van het huidige account vindt, en -u en --hp zijn het account en de thuismap waarmee de service draait. De regel ExecStart in het servicebestand is pm2 resurrect, dat bij het opstarten de opgeslagen app-lijst terugzet.

systemctl status pm2-deploy --no-pager | head -n 5
○ pm2-deploy.service - PM2 process manager
     Loaded: loaded (/etc/systemd/system/pm2-deploy.service; enabled; preset: enabled)
     Active: inactive (dead)
       Docs: https://pm2.keymetrics.io/

enabled betekent dat de service bij het opstarten draait. pm2 startup registreert de service alleen en start hem nu niet, daarom staat er inactive (dead), en de service draait vanaf de volgende keer opstarten.

🔲 pm2 list na herstart

sudo systemctl reboot

Maak na ongeveer 2 minuten opnieuw verbinding als deploy en controleer de status van de app.

uptime -p
up 2 minutes
pm2 list
┌────┬──────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id │ name     │ namespace   │ version │ mode    │ pid      │ uptime │ ↺    │ status    │ cpu      │ mem      │ user     │ watching │
├────┼──────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 0  │ hello    │ default     │ N/A     │ fork    │ 976      │ 108s   │ 0    │ online    │ 0%       │ 63.0mb   │ deploy   │ disabled │
└────┴──────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
host metrics | cpu: 0.3% | ram usage: 35.1% | disk: ⇓ 4kb/s ⇑ 4kb/s

Twee minuten nadat de server was opgestart, was de app al 108 seconden online: PM2 had de app samen met het opstarten weer gestart.

4️⃣ Nginx reverse proxy instellen

Omdat de app alleen verzoeken op 127.0.0.1:3000 accepteert, ontvangt Nginx de verzoeken die op poort 80 binnenkomen en geeft ze door aan de app. Zo'n opzet, waarbij een server vooraan verzoeken ontvangt en doorstuurt naar een server erachter, heet een reverse proxy.

🔲 apt-get install nginx

sudo -E apt-get install -y nginx
Setting up nginx-common (1.24.0-2ubuntu7.19) ...
Created symlink /etc/systemd/system/multi-user.target.wants/nginx.service → /usr/lib/systemd/system/nginx.service.
Setting up nginx (1.24.0-2ubuntu7.19) ...
nginx -v
nginx version: nginx/1.24.0 (Ubuntu)

De Nginx uit de Ubuntu 24.04-repository is versie 1.24.0, en bij de installatie wordt hij meteen ingesteld om bij het opstarten te draaien.

🔲 /etc/nginx/sites-available/hello

Maak het bestand /etc/nginx/sites-available/hello aan en zet de onderstaande inhoud erin.

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

proxy_pass is het app-adres waaraan verzoeken worden doorgegeven. De headers Host, X-Real-IP, X-Forwarded-For en X-Forwarded-Proto zijn waarden die worden meegegeven zodat de app het domein van het oorspronkelijke verzoek, het IP van de bezoeker en http of https kan kennen.

proxy_http_version 1.1 en de headers Upgrade en Connection zijn nodig om WebSocket-verbindingen tot aan de app door te geven. De headers Upgrade en Connection gaan na een proxy niet meer naar de app, daarom zet je ze direct in de configuratie.

sudo ln -sf /etc/nginx/sites-available/hello /etc/nginx/sites-enabled/hello

Nginx leest alleen configuraties waarvoor een link in de map sites-enabled staat.

🔲 nginx -t

sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
sudo systemctl reload nginx

Controleer eerst de syntaxis van de configuratie met nginx -t, en pas daarna bij succes de nieuwe configuratie toe met reload zonder Nginx te stoppen.

🔲 curl-verzoek met Host-header

curl -sS -i -H 'Host: example.com' http://127.0.0.1/ | head -n 4
HTTP/1.1 200 OK
Server: nginx/1.24.0 (Ubuntu)
Date: Sat, 07 Nov 2026 08:09:38 GMT
Content-Length: 16

Nadat de configuratie opnieuw was ingelezen, kreeg het verzoek met Host-header het antwoord van de app terug. De regel Server in de antwoordheaders is nginx, en Content-Length 16 is even groot als de regel hello from node die de app teruggeeft (16 bytes inclusief regeleinde). Om van buiten de server verbinding te maken, moet poort 80 open staan in de firewall.

🧭 Verzoeken aan de Node.js-app en herstart
Uitlegdiagram
🧭 Situatie
Verloop van het verzoek
🌐BrowserVerzoek aan example.com
➜
🧱NginxPoort 80, proxy_pass
➜
🟢Node.js-app127.0.0.1:3000
Procesbeheer
⚙️systemdService pm2-deploy
➜
🔁PM2App-lijst dump.pm2
➜
🟢Node.js-apphello
Nginx op poort 80 ontvangt het verzoek van de browser, geeft het door aan 127.0.0.1:3000 uit proxy_pass en stuurt het antwoord van de app ongewijzigd terug.
Wanneer het app-proces stopt, start PM2 dezelfde app meteen opnieuw. Na het stoppen met kill -9 liet pm2 list 3 seconden later de app online zien met een nieuwe pid.
Wanneer de server opstart, voert systemd de service pm2-deploy uit, en PM2 start de apps opnieuw volgens de lijst die met pm2 save is opgeslagen.

5️⃣ HTTPS toepassen met Certbot

Met de Nginx-plug-in van Certbot kun je het certificaat aanvragen en de Nginx-configuratie in één keer aanpassen. Voer het commando hieronder uit wanneer het domein al naar het IP van de server wijst.

sudo certbot --nginx

De installatie van Certbot en de test van automatisch vernieuwen staan in het artikel hieronder; het aanvraagcommando in dat artikel is voor Apache, dus vervang alleen de aanvraagstap door het commando hierboven.

🔢 FAQ & aanbevolen content

Als Nginx het app-adres in proxy_pass niet kan bereiken, geeft het 502 Bad Gateway terug. Nadat de app met pm2 stop hello was gestopt, gaf hetzelfde verzoek 502, en in /var/log/nginx/error.log stond de regel [connect() failed (111: Connection refused) while connecting to upstream] met het adres waarmee verbinding werd gezocht, http://127.0.0.1:3000/. Controleer eerst in pm2 list of de app online is, en of de poort waarop de app verzoeken ontvangt overeenkomt met het adres in proxy_pass. Nadat de app opnieuw was gestart, kwam hetzelfde verzoek terug met 200.

PM2 slaat de uitvoer van de app op als bestanden per app-naam in de map .pm2/logs onder de thuismap van het account dat de app startte. pm2 logs hello --nostream --lines 5 toonde de laatste regels van twee bestanden, /home/deploy/.pm2/logs/hello-out.log en hello-error.log, en het out-bestand bevatte het verzoek GET / dat de app had gelogd. Zonder --nostream blijft het nieuwe logregels tonen zodra ze binnenkomen.

De service voor automatisch starten verwijder je met pm2 unstartup systemd. Nadat je apps hebt toegevoegd of verwijderd, voer je pm2 save opnieuw uit zodat bij het opstarten de gewijzigde lijst wordt gebruikt. Ook bij het uitvoeren van pm2 startup werden deze twee commando's aan het eind van de uitvoer genoemd.

ℹ️ Affiliatemelding
De content van deze site bevat affiliatelinks. Koopt een bezoeker via een van deze links een product of dienst, dan ontvangt deze site een commissie van de verkoper. Het bedrag dat de koper betaalt (bij acties gaat het omlaag ↓) wordt daardoor niet hoger. De vermelde prijzen, kortingen en voorraad gelden op het moment van schrijven en kunnen afwijken; controleer ze daarom vóór je aankoop bij de verkoper. Producten worden gekozen en beoordeeld volgens onze eigen criteria, en commissies hebben geen invloed op de volgorde of de inhoud van de beoordelingen.

Geef een reactie

Je e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *

Laat zien dat je geen machine bent: 4   +   7   =