PM2 + Nginx Reverse Proxy: Node.js-App mit Port 80 verbinden
Auf einem Ubuntu 24.04 Server haben wir einen Node.js PM2 Nginx Reverse Proxy eingerichtet: PM2 führt die App aus, und Nginx leitet Anfragen, die auf Port 80 ankommen, an die App weiter. Außerdem haben wir geprüft, dass PM2 die App wieder startet, auch wenn der App-Prozess beendet oder der Server neu gestartet wird.
Die Ergebnisboxen unten zeigen die Ausgabe von Befehlen, die mit dem normalen Benutzer deploy ausgeführt wurden. Als Domain steht der Beispielwert example.com, und die Anfragen wurden innerhalb des Servers mit einem Host-Header gesendet.
1️⃣ Node.js installieren
Node.js wird installiert, indem das NodeSource-Repository in apt eingetragen wird, und als Version haben wir Node.js 26 gewählt, die LTS-Version mit Stand November 2026.
🔲 NodeSource setup_26.x
export DEBIAN_FRONTEND=noninteractiveWenn du diesen Wert zuerst setzt, erscheint während der Paketinstallation kein Fenster, das nach Einstellungen fragt. Die Option -E des sudo-Befehls unten gibt diesen Wert auch an den Befehl weiter, der mit root-Rechten läuft.
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 -ySobald das Skript das Repository eingetragen hat, kannst du Node.js wie ein Ubuntu-Paket mit apt installieren.
🔲 apt-get install nodejs
sudo -E apt-get install -y nodejsSetting 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 -vv26.13.0npm -v11.20.0npm ist im Paket nodejs enthalten, daher installierst du es nicht separat.
2️⃣ Node.js-Test-App
Um zu sehen, ob PM2 und Nginx richtig verbunden sind, erstellst du eine Test-App, die bei einer Anfrage eine Zeile Text zurückgibt.
🔲 app.js
mkdir -p ~/helloErstelle unter deinem Home-Ordner den Ordner hello und lege darin die Datei app.js an.
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');
APPDie App nimmt Anfragen nur auf Port 3000 von 127.0.0.1 an, daher lässt sie sich von außerhalb des Servers nicht direkt öffnen. Anfragen von außen nimmt Nginx entgegen und leitet sie an diesen Port weiter.
3️⃣ App mit PM2 ausführen und Autostart
PM2 ist ein Prozessmanager, der Node.js-Apps im Hintergrund ausführt und eine App wieder startet, wenn ihr Prozess endet. In diesem Abschnitt richten wir auch den Autostart ein, der die App beim Booten des Servers wieder hochfährt.
🔲 npm install -g pm2
sudo npm install -g pm2added 45 packages in 5spm2 -v[PM2] Spawning PM2 daemon with pm2_home=/home/deploy/.pm2
[PM2] PM2 Successfully daemonized
7.1.2Beim ersten pm2-Befehl startet auch der PM2-Daemon, und die installierte PM2-Version ist 7.1.2.
🔲 pm2 start
cd ~/helloWechsle in den Ordner mit app.js und führe die App dann mit PM2 aus.
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 ist der App-Name, der in der PM2-Liste angezeigt wird, und die folgenden Befehle sprechen die App über diesen Namen an.
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/sonline in der Spalte status bedeutet, dass die App läuft, und die Spalte ↺ zeigt, wie oft PM2 die App neu gestartet hat.
🔲 curl 127.0.0.1:3000
Wenn du innerhalb des Servers eine Anfrage sendest, kommt die Textzeile, die die App zurückgibt, unverändert an.
curl -sS http://127.0.0.1:3000/hello from node🔲 pm2 restart
Nachdem du den App-Code geändert hast, startest du die App neu, damit die Änderung greift.
pm2 restart helloUse --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/spm2 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/sNach dem Neustart wechselte die pid von 17257 zu 17306, und die Spalte ↺ stieg auf 1.
🔲 kill -9
Ein plötzliches Ende des App-Prozesses lässt sich mit kill -9 herbeiführen, und pm2 pid gibt die Prozessnummer der App aus.
kill -9 "$(pm2 pid hello)"Prüfe die PM2-Liste nach 3 Sekunden noch einmal.
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/sPM2 hatte die App mit der neuen pid 17797 wieder gestartet, sie war online, und die Spalte ↺ war auf 2 gestiegen.
🔲 pm2 save
pm2 save[PM2] Saving current process list...
[PM2] Successfully saved in /home/deploy/.pm2/dump.pm2pm2 save speichert die Liste der gerade laufenden Apps in der Datei dump.pm2, und beim Booten startet PM2 die Apps nach dieser Liste wieder.
🔲 pm2 startup
Damit PM2 beim Booten läuft, registrierst du PM2 als systemd-Dienst. Der Befehl unten erstellt den Dienst mit dem Namen des aktuellen Kontos und dessen Home-Ordner.
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 sorgt dafür, dass pm2, das mit root-Rechten läuft, den node-Pfad des aktuellen Kontos findet, und -u und --hp sind das Konto und der Home-Ordner, mit denen der Dienst läuft. Die Zeile ExecStart in der Dienstdatei ist pm2 resurrect, das beim Booten die gespeicherte App-Liste wiederherstellt.
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 bedeutet, dass der Dienst beim Booten ausgeführt wird. pm2 startup registriert den Dienst nur und startet ihn jetzt nicht, daher steht dort inactive (dead), und der Dienst läuft ab dem nächsten Booten.
🔲 pm2 list nach dem Neustart
sudo systemctl rebootVerbinde dich nach etwa 2 Minuten erneut als deploy und prüfe den Zustand der App.
uptime -pup 2 minutespm2 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/sZwei Minuten nach dem Hochfahren des Servers war die App bereits seit 108 Sekunden online, PM2 hatte sie also mit dem Booten wieder gestartet.
4️⃣ Nginx Reverse Proxy einrichten
Da die App Anfragen nur auf 127.0.0.1:3000 annimmt, nimmt Nginx die Anfragen auf Port 80 entgegen und leitet sie an die App weiter. Einen solchen Aufbau, bei dem ein Server vorne Anfragen annimmt und an einen Server dahinter weiterleitet, nennt man Reverse Proxy.
🔲 apt-get install nginx
sudo -E apt-get install -y nginxSetting 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 -vnginx version: nginx/1.24.0 (Ubuntu)Das Nginx im Ubuntu 24.04-Repository ist Version 1.24.0 und wird bei der Installation gleich für den Start beim Booten eingetragen.
🔲 /etc/nginx/sites-available/hello
Erstelle die Datei /etc/nginx/sites-available/hello und füge den folgenden Inhalt ein.
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 ist die App-Adresse, an die Anfragen weitergegeben werden. Die Header Host, X-Real-IP, X-Forwarded-For und X-Forwarded-Proto sind Werte, die mitgegeben werden, damit die App die Domain der ursprünglichen Anfrage, die IP des Besuchers und http oder https erkennen kann.
proxy_http_version 1.1 und die Header Upgrade und Connection braucht man, um WebSocket-Verbindungen bis zur App durchzureichen. Die Header Upgrade und Connection werden nach einem Proxy nicht mehr an die App weitergegeben, daher setzt man sie direkt in der Konfiguration.
sudo ln -sf /etc/nginx/sites-available/hello /etc/nginx/sites-enabled/helloNginx liest nur Konfigurationen, für die im Ordner sites-enabled ein Link angelegt ist.
🔲 nginx -t
sudo nginx -tnginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successfulsudo systemctl reload nginxPrüfe zuerst mit nginx -t die Syntax der Konfiguration, und wenn sie passt, übernimmst du die neue Konfiguration mit reload, ohne Nginx anzuhalten.
🔲 curl-Anfrage mit Host-Header
curl -sS -i -H 'Host: example.com' http://127.0.0.1/ | head -n 4HTTP/1.1 200 OK
Server: nginx/1.24.0 (Ubuntu)
Date: Sat, 07 Nov 2026 08:09:38 GMT
Content-Length: 16Nach dem Neuladen der Konfiguration kam auf die Anfrage mit Host-Header die Antwort der App zurück. Die Zeile Server in den Antwort-Headern ist nginx, und Content-Length 16 entspricht der Größe der Zeile hello from node, die die App zurückgibt (16 Byte mit Zeilenumbruch). Für Zugriffe von außerhalb des Servers muss Port 80 in der Firewall offen sein.
5️⃣ HTTPS mit Certbot einrichten
Mit dem Nginx-Plugin von Certbot kannst du das Zertifikat ausstellen und die Nginx-Konfiguration in einem Schritt ändern. Führe den Befehl unten aus, wenn die Domain bereits auf die Server-IP zeigt.
sudo certbot --nginxDie Installation von Certbot und den Test der automatischen Erneuerung findest du im Beitrag unten; der Ausstellungsbefehl dort ist für Apache, ersetze also nur den Schritt der Ausstellung durch den Befehl oben.
🔢 FAQ & empfohlene Inhalte
Dienst-Logs
journalctl-Befehl: Verwendung, Logs nach Dienst und Journal-Größe aufräumen
Ein Beitrag darüber, wie du die Logs von systemd-Diensten wie nginx oder pm2-deploy je Dienst liest.
ℹ️ Affiliate-Hinweis
Die Inhalte dieser Website enthalten Affiliate-Links. Kauft ein Besucher über einen dieser Links ein Produkt oder eine Dienstleistung, erhält die Website vom Verkäufer eine Provision. Der Betrag, den der Käufer zahlt(bei Aktionsrabatten sinkt er ↓), steigt dadurch nicht. Angegebene Preise, Rabatte und Lagerbestände entsprechen dem Stand bei Erstellung und können abweichen; prüfe sie daher vor dem Kauf beim Verkäufer. Produkte werden nach eigenen Kriterien ausgewählt und bewertet, und Provisionen beeinflussen weder die Reihenfolge noch den Inhalt der Bewertungen.