Reverse proxy PM2 + Nginx: collega un'app Node.js alla porta 80

Ambiente di testFamiglia DebianUbuntu 24.04.5 LTSaccount utente normale
Gestione dell'app Node.js e reverse proxy verificati

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.

🎯 Punti chiave
🗂️ Flusso della richiesta
📂 NginxReverse proxy
📄 CertbotCertificato HTTPS
📂 PM2Esecuzione e avvio automatico
📄 App Node.js
Eseguire l'app con PM2 e avviarla al boot
Nginx passa all'app le richieste sulla porta 80
Prerequisiti
ServerUbuntu 24.04 con la configurazione iniziale fattasudo abilitato su un account utente normaleDominio necessario per HTTPS

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=noninteractive

Se 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 -y

Quando 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 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 è 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 ~/hello

Crea 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');
APP

L'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 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

Al primo comando pm2 parte anche il demone di PM2, e la versione di PM2 installata è la 7.1.2.

🔲 pm2 start

cd ~/hello

Spostati 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/s

online 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 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

Dopo 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/s

PM2 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.pm2

pm2 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 reboot

Dopo circa 2 minuti ricollegati come deploy e controlla lo stato dell'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

Due 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 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)

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/hello

Nginx legge solo le configurazioni che hanno un collegamento nella cartella sites-enabled.

🔲 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

Controlla 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 4
HTTP/1.1 200 OK
Server: nginx/1.24.0 (Ubuntu)
Date: Sat, 07 Nov 2026 08:09:38 GMT
Content-Length: 16

Dopo 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.

🧭 Richieste all'app Node.js e riavvio
Diagramma esplicativo
🧭 Situazione
Flusso della richiesta
🌐BrowserRichiesta a example.com
➜
🧱NginxPorta 80, proxy_pass
➜
🟢App Node.js127.0.0.1:3000
Gestione dei processi
⚙️systemdServizio pm2-deploy
➜
🔁PM2Elenco app dump.pm2
➜
🟢App Node.jshello
Nginx sulla porta 80 riceve la richiesta del browser, la passa a 127.0.0.1:3000 indicato in proxy_pass e restituisce la risposta dell'app così com'è.
Quando il processo dell'app si ferma, PM2 riavvia subito la stessa app. Dopo averla fermata con kill -9, pm2 list 3 secondi dopo mostrava l'app online con un nuovo pid.
All'avvio del server, systemd esegue il servizio pm2-deploy, e PM2 riavvia le app in base all'elenco salvato con pm2 save.

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 --nginx

L'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

Se Nginx non riesce a collegarsi all'indirizzo dell'app indicato in proxy_pass, restituisce 502 Bad Gateway. Dopo aver fermato l'app con pm2 stop hello, la stessa richiesta ha dato 502, e in /var/log/nginx/error.log è rimasta la riga [connect() failed (111: Connection refused) while connecting to upstream] con l'indirizzo che cercava di raggiungere, http://127.0.0.1:3000/. Controlla prima in pm2 list che l'app sia online e che la porta su cui l'app riceve le richieste coincida con l'indirizzo di proxy_pass. Dopo il riavvio dell'app, la stessa richiesta è tornata con 200.

PM2 salva l'output dell'app in file con il nome di ciascuna app nella cartella .pm2/logs, sotto la cartella home dell'account che l'ha avviata. Eseguendo pm2 logs hello --nostream --lines 5 sono comparse le ultime righe di due file, /home/deploy/.pm2/logs/hello-out.log e hello-error.log, e il file out conteneva la richiesta GET / registrata dall'app. Senza --nostream continua a mostrare i nuovi log man mano che arrivano.

Il servizio di avvio automatico si rimuove con pm2 unstartup systemd. Dopo aver aggiunto o tolto app, esegui di nuovo pm2 save perché all'avvio le app partano secondo l'elenco modificato. Anche eseguendo pm2 startup, questi due comandi erano indicati alla fine dell'output.

ℹ️ 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.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Dimostra di essere umano: 7   +   3   =