Reverse proxy PM2 + Nginx: collega un'app Node.js alla porta 80
Su un server Ubuntu 24.04 abbiamo configurato un reverse proxy Node.js con PM2 e Nginx: PM2 si occupa di eseguire l'app e Nginx passa all'app le richieste che arrivano sulla porta 80. Abbiamo anche verificato che PM2 riavvia l'app anche quando il suo processo viene terminato a forza o il server viene riavviato.
I riquadri dei risultati qui sotto mostrano l'output dei comandi eseguiti con l'utente normale deploy. Al posto del dominio c'è il valore di esempio example.com, e le richieste sono state inviate dall'interno del server con l'header Host.
1️⃣ Installare Node.js
Node.js si installa aggiungendo il repository NodeSource ad apt, e abbiamo scelto Node.js 26, la versione LTS a novembre 2026.
🔲 NodeSource setup_26.x
export DEBIAN_FRONTEND=noninteractiveSe imposti prima questo valore, durante l'installazione dei pacchetti non compare la finestra che chiede le impostazioni. L'opzione -E del comando sudo qui sotto passa questo valore anche al comando eseguito con i permessi di root.
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 -yQuando lo script ha finito di aggiungere il repository, puoi installare Node.js con apt come qualsiasi pacchetto Ubuntu.
🔲 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 è incluso nel pacchetto nodejs, quindi non devi installarlo a parte.
2️⃣ App di prova Node.js
Per vedere se PM2 e Nginx sono collegati bene, crea un'app di prova che restituisce una riga di testo quando riceve una richiesta.
🔲 app.js
mkdir -p ~/helloCrea la cartella hello nella tua cartella home e scrivi al suo interno il file 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');
APPL'app accetta richieste solo sulla porta 3000 di 127.0.0.1, quindi non si può aprire direttamente dall'esterno del server. Configureremo Nginx per ricevere le richieste esterne e passarle a questa porta.
3️⃣ Eseguire l'app con PM2 e avvio automatico
PM2 è un gestore di processi che esegue le app Node.js in background e riavvia un'app quando il suo processo si ferma. In questa sezione configuriamo anche l'avvio automatico, che rimette in piedi l'app all'avvio del server.
🔲 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.2Al primo comando pm2 parte anche il demone di PM2, e la versione di PM2 installata è la 7.1.2.
🔲 pm2 start
cd ~/helloSpostati nella cartella che contiene app.js, poi esegui l'app con 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 è il nome dell'app mostrato nell'elenco di PM2, e i comandi successivi indicano l'app con questo nome.
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 nella colonna status significa che l'app è in esecuzione, e la colonna ↺ è il numero di volte in cui PM2 ha riavviato l'app.
🔲 curl 127.0.0.1:3000
Se invii una richiesta dall'interno del server, la riga di testo restituita dall'app appare così com'è.
curl -sS http://127.0.0.1:3000/hello from node🔲 pm2 restart
Dopo aver modificato il codice dell'app, riavviala per applicare la modifica.
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/sDopo il riavvio il pid è passato da 17257 a 17306 e la colonna ↺ è salita a 1.
🔲 kill -9
Puoi simulare un arresto improvviso del processo dell'app con kill -9, e pm2 pid mostra il numero di processo dell'app.
kill -9 "$(pm2 pid hello)"Ricontrolla l'elenco di PM2 dopo 3 secondi.
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 aveva riavviato l'app con il nuovo pid 17797 ed era online, e la colonna ↺ era salita a 2.
🔲 pm2 save
pm2 save[PM2] Saving current process list...
[PM2] Successfully saved in /home/deploy/.pm2/dump.pm2pm2 save salva l'elenco delle app in esecuzione nel file dump.pm2, e all'avvio PM2 riavvia le app in base a questo elenco.
🔲 pm2 startup
Per far partire PM2 all'avvio, registra PM2 come servizio systemd. Il comando qui sotto crea il servizio con il nome dell'account attuale e la sua cartella home.
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 è la parte che permette a pm2, eseguito con i permessi di root, di trovare il percorso di node dell'account attuale, e -u e --hp sono l'account e la cartella home con cui gira il servizio. La riga ExecStart del file di servizio è pm2 resurrect, che all'avvio ripristina l'elenco di app salvato.
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 significa che il servizio parte all'avvio. pm2 startup registra soltanto il servizio senza avviarlo ora, per questo risulta inactive (dead), e il servizio parte dal prossimo avvio.
🔲 pm2 list dopo il riavvio
sudo systemctl rebootDopo circa 2 minuti ricollegati come deploy e controlla lo stato dell'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/sDue minuti dopo l'accensione del server, l'app era già online da 108 secondi: PM2 l'aveva riavviata insieme all'avvio.
4️⃣ Configurare il reverse proxy Nginx
Poiché l'app accetta richieste solo su 127.0.0.1:3000, Nginx riceve le richieste che arrivano sulla porta 80 e le passa all'app. Una configurazione come questa, in cui un server davanti riceve le richieste e le inoltra a un server dietro, si chiama 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)Il Nginx del repository di Ubuntu 24.04 è la versione 1.24.0, e con l'installazione viene già registrato per partire all'avvio.
🔲 /etc/nginx/sites-available/hello
Crea il file /etc/nginx/sites-available/hello e inserisci il contenuto seguente.
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 è l'indirizzo dell'app a cui vengono passate le richieste. Gli header Host, X-Real-IP, X-Forwarded-For e X-Forwarded-Proto sono valori passati perché l'app conosca il dominio della richiesta originale, l'IP del visitatore e se la richiesta era http o https.
proxy_http_version 1.1 e gli header Upgrade e Connection servono per far arrivare le connessioni WebSocket fino all'app. Gli header Upgrade e Connection non vengono inoltrati all'app dopo un proxy, quindi si inseriscono direttamente nella configurazione.
sudo ln -sf /etc/nginx/sites-available/hello /etc/nginx/sites-enabled/helloNginx legge solo le configurazioni che hanno un collegamento nella cartella sites-enabled.
🔲 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 nginxControlla prima la sintassi della configurazione con nginx -t e, se passa, applica la nuova configurazione con reload senza fermare Nginx.
🔲 Richiesta curl con header Host
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: 16Dopo aver ricaricato la configurazione, la richiesta inviata con l'header Host ha ricevuto la risposta dell'app. La riga Server negli header di risposta è nginx, e Content-Length 16 ha la stessa dimensione della riga hello from node restituita dall'app (16 byte compreso il ritorno a capo). Per collegarti dall'esterno del server, la porta 80 deve essere aperta nel firewall.
5️⃣ Attivare HTTPS con Certbot
Con il plugin Nginx di Certbot puoi ottenere il certificato e modificare la configurazione di Nginx in un solo passaggio. Esegui il comando qui sotto quando il dominio punta già all'IP del server.
sudo certbot --nginxL'installazione di Certbot e il test del rinnovo automatico sono nell'articolo qui sotto; il comando di emissione di quell'articolo è per Apache, quindi sostituisci solo il passaggio di emissione con il comando qui sopra.
🔢 FAQ & contenuti consigliati
Log dei servizi
Comando journalctl: uso, log per servizio e pulizia delle dimensioni del journal
Articolo su come leggere, servizio per servizio, i log dei servizi systemd come nginx e pm2-deploy.
ℹ️ Informativa sull'affiliazione
I contenuti di questo sito includono link di affiliazione. Se un visitatore acquista un prodotto o un servizio tramite uno di questi link, il sito riceve una commissione dal venditore. In questo processo l'importo pagato dall'acquirente (durante le promozioni l'importo scende↓) non aumenta. Prezzi, sconti e disponibilità indicati si riferiscono al momento della stesura e possono differire da quelli reali: prima di acquistare verifica sempre presso il venditore. I prodotti vengono scelti e valutati secondo criteri propri, e le commissioni non influiscono sull'ordine di presentazione né sul contenuto delle valutazioni.