Come configurare Nginx come reverse proxy
Configura Nginx come reverse proxy per Node.js, Ollama e altri backend. Copre proxy_pass, inoltro degli header, supporto WebSocket, bilanciamento del carico con upstream e tuning dei timeout in produzione, con verifiche a ogni passaggio.
Un reverse proxy si interpone tra i client e la tua applicazione backend. Nginx riceve le richieste in arrivo, le inoltra a un server backend (Node.js, Python, Go, Ollama) e restituisce la risposta al client. Questo ti permette di aggiungere terminazione TLS, bilanciamento del carico, caching e controllo degli accessi senza modificare la tua applicazione.
Questo tutorial copre la configurazione di Nginx come reverse proxy, da un semplice proxy_pass fino al proxy WebSocket, bilanciamento del carico con upstream e un setup Ollama pronto per la produzione.
Prerequisiti
Prima di iniziare, ti servono:
- Un VPS con Debian 12 o Ubuntu 24.04, con accesso root o sudo
- Nginx installato e in esecuzione (Installare Nginx su Debian e Ubuntu)
- Un'applicazione backend in ascolto su una porta locale (il tutorial include esempi di test)
- Familiarità di base con la struttura dei file di configurazione Nginx (Struttura dei file di configurazione Nginx)
Verifica che Nginx sia in esecuzione:
sudo systemctl status nginx
Dovresti vedere active (running) nell'output.
Come si configura proxy_pass in Nginx?
La direttiva proxy_pass dice a Nginx dove inoltrare le richieste. Si inserisce in un blocco location all'interno di un blocco server. Nginx invia la richiesta del client all'URL backend specificato e restituisce la risposta in streaming. La direttiva accetta URL HTTP e HTTPS, e può puntare a un indirizzo IP, un nome di dominio o un socket Unix.
Crea un nuovo file server block:
sudo nano /etc/nginx/sites-available/app.conf
Aggiungi una configurazione reverse proxy minimale:
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Abilita il sito e testa la configurazione:
sudo ln -s /etc/nginx/sites-available/app.conf /etc/nginx/sites-enabled/
sudo nginx -t
Se nginx -t restituisce syntax is ok e test is successful, ricarica:
sudo systemctl reload nginx
Verifica che il proxy funzioni:
curl -I http://app.example.com
Gli header della risposta dovrebbero provenire dalla tua applicazione backend. Se vedi un 502 Bad Gateway, il backend non è in esecuzione sulla porta 3000.
Cosa succede con il trailing slash in proxy_pass?
Il trailing slash (barra finale) in proxy_pass controlla la riscrittura dell'URI. Questa è una delle cause di errore più frequenti nella configurazione.
| Configurazione | Richiesta | Inoltrata al backend |
|---|---|---|
proxy_pass http://127.0.0.1:3000 |
/app/users |
http://127.0.0.1:3000/app/users |
proxy_pass http://127.0.0.1:3000/ |
/app/users |
http://127.0.0.1:3000/users |
proxy_pass http://127.0.0.1:3000/v2/ |
/app/users |
http://127.0.0.1:3000/v2/users |
proxy_pass http://127.0.0.1:3000/v2 |
/app/users |
http://127.0.0.1:3000/v2users |
La regola: quando proxy_pass include un URI (qualsiasi cosa dopo host:port, anche solo /), Nginx rimuove il prefisso corrispondente al location dall'URI della richiesta e aggiunge il resto all'URI di proxy_pass. Quando proxy_pass non ha un URI, il percorso originale della richiesta passa invariato.
Per un blocco location /app/, si applicano gli esempi sopra. Nota la quarta riga: senza trailing slash su /v2, il percorso diventa /v2users invece di /v2/users. Includi sempre un trailing slash quando specifichi un percorso.
Quali header dovresti inoltrare al backend?
Senza una configurazione esplicita degli header, la tua applicazione backend non può vedere l'indirizzo IP reale del client, il protocollo originale o l'hostname richiesto. Nginx li sostituisce per impostazione predefinita. Inoltrale manualmente.
| Header | Valore | Scopo |
|---|---|---|
Host |
$host |
Preserva l'header Host originale, così il backend sa quale dominio è stato richiesto |
X-Real-IP |
$remote_addr |
Passa l'indirizzo IP del client al backend |
X-Forwarded-For |
$proxy_add_x_forwarded_for |
Aggiunge l'IP del client alla catena dei proxy |
X-Forwarded-Proto |
$scheme |
Indica al backend se la richiesta originale usava HTTP o HTTPS |
Aggiungi questi header al tuo blocco location:
location / {
proxy_pass http://127.0.0.1:3000;
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;
}
Dopo aver ricaricato Nginx, verifica che gli header raggiungano il backend. Se la tua app registra gli header in arrivo, controllali:
sudo systemctl reload nginx
curl -s http://app.example.com/headers
Il tuo backend dovrebbe mostrare X-Real-IP corrispondente all'IP del client, non 127.0.0.1. Se mostra 127.0.0.1, le direttive proxy_set_header mancano o sono nel contesto sbagliato.
Nota sulla sicurezza: usa $proxy_add_x_forwarded_for invece di $remote_addr per X-Forwarded-For. Questo aggiunge l'IP del client a qualsiasi header X-Forwarded-For esistente. Se imposti solo $remote_addr, perdi la catena dei proxy, cosa che compromette il tracciamento degli IP dietro proxy multipli. Se Nginx è il tuo unico proxy, i due valori sono equivalenti.
Come si configura un reverse proxy per un'applicazione Node.js?
Ecco un server block completo per il proxy di un'applicazione Node.js in esecuzione sulla porta 3000. Questo esempio include l'inoltro degli header, il supporto WebSocket e l'occultamento della versione.
Crea il server block:
sudo nano /etc/nginx/sites-available/nodeapp.conf
server {
listen 80;
server_name nodeapp.example.com;
# Hide Nginx version in responses
server_tokens off;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
# Header forwarding
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;
# WebSocket support
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Timeouts
proxy_connect_timeout 60s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
}
}
Abilita, testa e ricarica:
sudo ln -s /etc/nginx/sites-available/nodeapp.conf /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
Verifica che il proxy funzioni:
curl -I http://nodeapp.example.com
Occhio: la risposta non dovrebbe contenere una riga Server: nginx/1.x.x. La direttiva server_tokens off nasconde il numero di versione. Rivelare la versione aiuta gli attaccanti a prendere di mira vulnerabilità note.
Per la terminazione TLS con Let's Encrypt, vedi Nginx SSL/TLS con Let's Encrypt.
Come si configura il proxy WebSocket con Nginx?
Le connessioni WebSocket iniziano come una richiesta HTTP con un header Upgrade, poi passano a una connessione bidirezionale persistente. Nginx usa HTTP/1.0 per impostazione predefinita per le connessioni upstream, che non supporta il meccanismo Upgrade. Servono tre direttive per far funzionare WebSocket: impostare proxy_http_version a 1.1, inoltrare l'header Upgrade e impostare l'header Connection a "upgrade".
Per le location che servono sia traffico HTTP normale che WebSocket, usa la direttiva map per impostare l'header Connection in modo condizionale. Aggiungi questo al blocco http in /etc/nginx/nginx.conf:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
Poi usa $connection_upgrade nel tuo server block:
server {
listen 80;
server_name ws.example.com;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Prevent idle timeout killing WebSocket connections
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}
Il proxy_read_timeout predefinito è 60 secondi. Se nessun dato attraversa la connessione in quella finestra, Nginx la chiude. Impostalo più alto per le connessioni WebSocket. La tua applicazione dovrebbe inviare frame WebSocket ping a un intervallo inferiore a questo timeout.
Testa la configurazione:
sudo nginx -t && sudo systemctl reload nginx
Per verificare la connettività WebSocket, installa wscat e connettiti:
npm install -g wscat
wscat -c ws://ws.example.com/
Se la connessione si apre, il proxy WebSocket funziona. Se ricevi unexpected server response (200), gli header Upgrade non vengono inoltrati. Verifica che la direttiva map sia all'interno del blocco http, non dentro un blocco server.
Come si configura un reverse proxy per Ollama (AI self-hosted)?
Ollama serve l'inferenza LLM sulla porta 11434 per impostazione predefinita e si collega a 127.0.0.1. Passarlo attraverso un proxy Nginx ti permette di aggiungere TLS, autenticazione e controllo degli accessi senza modificare la configurazione di Ollama. La differenza chiave rispetto al proxy standard: l'inferenza LLM può richiedere minuti e le risposte in streaming necessitano del buffering disabilitato.
Crea il server block:
sudo nano /etc/nginx/sites-available/ollama.conf
server {
listen 80;
server_name ollama.example.com;
server_tokens off;
# Restrict access to specific IPs
allow 192.168.1.0/24;
allow 10.0.0.0/8;
deny all;
location / {
proxy_pass http://127.0.0.1:11434;
proxy_http_version 1.1;
# Header forwarding
proxy_set_header Host localhost:11434;
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 Connection '';
# Disable buffering for streaming responses
proxy_buffering off;
proxy_cache off;
chunked_transfer_encoding on;
# Extended timeouts for LLM inference
proxy_connect_timeout 300s;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
}
}
Abilita e testa:
sudo ln -s /etc/nginx/sites-available/ollama.conf /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
Verifica che Ollama risponda attraverso il proxy:
curl http://ollama.example.com/api/tags
Dovresti vedere una risposta JSON con l'elenco dei modelli disponibili. Se ricevi un 403 Forbidden, l'IP del tuo client non è nella lista allow. Se ricevi un 502 Bad Gateway, Ollama non è in esecuzione:
sudo systemctl status ollama
Testa un completamento in streaming:
curl -N http://ollama.example.com/api/generate -d '{
"model": "llama3.2",
"prompt": "Hello",
"stream": true
}'
Occhio: il flag -N disabilita il buffering dell'output di curl. Dovresti vedere i token arrivare uno alla volta. Se l'intera risposta arriva tutta insieme, proxy_buffering off non è impostato o viene sovrascritto.
Perché queste impostazioni differiscono da un proxy standard:
proxy_buffering off: Nginx normalmente accumula le risposte del backend in un buffer e le invia in blocco. Per lo streaming LLM, vuoi che ogni token venga inviato al client immediatamente.proxy_read_timeout 600s: l'inferenza LLM su modelli grandi può richiedere diversi minuti. Il timeout predefinito di 60 secondi interromperebbe la connessione durante la generazione.proxy_set_header Host localhost:11434: Ollama verifica l'header Host e rifiuta le richieste che non corrispondono al suo indirizzo di bind configurato.proxy_set_header Connection '': svuota l'header Connection per prevenire problemi di keep-alive con lo streaming chunked.
Per la terminazione TLS e l'esposizione sicura di Ollama su internet, vedi Nginx SSL/TLS con Let's Encrypt. Non esporre mai Ollama senza controllo degli accessi. Combina restrizioni IP con HTTP Basic Auth o validazione API key per l'uso in produzione.
Per aggiungere HTTP Basic Auth oltre alle restrizioni IP:
sudo apt install apache2-utils
sudo htpasswd -c /etc/nginx/.ollama_htpasswd apiuser
sudo chmod 640 /etc/nginx/.ollama_htpasswd
sudo chown root:www-data /etc/nginx/.ollama_htpasswd
Poi aggiungi al blocco location in ollama.conf:
auth_basic "Ollama API";
auth_basic_user_file /etc/nginx/.ollama_htpasswd;
Verifica che i permessi del file siano corretti:
ls -la /etc/nginx/.ollama_htpasswd
Il file dovrebbe mostrare -rw-r----- con owner root e gruppo www-data. Restringere i permessi impedisce agli altri utenti del server di leggere gli hash delle password.
Come si configura il bilanciamento del carico con upstream?
Il blocco upstream definisce un gruppo di server backend tra cui Nginx distribuisce le richieste. Per impostazione predefinita, Nginx usa il round-robin pesato. Ogni server riceve una quota di richieste proporzionale al suo peso (peso predefinito: 1).
Aggiungi un blocco upstream prima del tuo blocco server:
upstream app_backends {
server 127.0.0.1:3000;
server 127.0.0.1:3001;
server 127.0.0.1:3002;
}
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://app_backends;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
Metodi di bilanciamento del carico
Round-robin (predefinito): le richieste vengono distribuite equamente tra i server. Non serve nessuna direttiva.
Least connections (minor numero di connessioni): invia le richieste al server con meno connessioni attive. Migliore per backend con tempi di risposta variabili:
upstream app_backends {
least_conn;
server 127.0.0.1:3000;
server 127.0.0.1:3001;
}
IP hash: lega ogni IP client a un backend specifico. Utile per la persistenza delle sessioni senza cookie sticky:
upstream app_backends {
ip_hash;
server 127.0.0.1:3000;
server 127.0.0.1:3001;
}
Health check con max_fails e fail_timeout
Nginx monitora passivamente lo stato dei backend. Se un server non risponde, Nginx lo segna come non disponibile e smette di inviargli richieste per un periodo.
upstream app_backends {
server 127.0.0.1:3000 max_fails=3 fail_timeout=30s;
server 127.0.0.1:3001 max_fails=3 fail_timeout=30s;
server 127.0.0.1:3002 backup;
}
| Parametro | Predefinito | Descrizione |
|---|---|---|
max_fails |
1 | Numero di tentativi falliti entro fail_timeout prima di segnare il server come non disponibile |
fail_timeout |
10s | Finestra per contare i fallimenti e durata dello stato non disponibile |
backup |
- | Il server riceve richieste solo quando tutti i server primari sono down |
weight |
1 | Quota relativa di richieste nel round-robin |
Dopo aver configurato l'upstream, testa e ricarica:
sudo nginx -t && sudo systemctl reload nginx
Verifica che il bilanciamento del carico funzioni inviando richieste multiple e controllando quale backend risponde:
for i in $(seq 1 6); do curl -s http://app.example.com/health; echo; done
Se i tuoi backend restituiscono un identificativo nella risposta, dovresti vedere le richieste distribuite tra di essi.
Come si regolano il buffering e i timeout del proxy?
Nginx accumula le risposte del backend in un buffer per impostazione predefinita. Legge l'intera risposta dal backend in memoria (o su disco se supera il buffer), poi la invia al client. Questo è efficiente per la maggior parte delle applicazioni, ma sbagliato per lo streaming, gli eventi Server-Sent Events (SSE) o il long-polling.
Direttive timeout
| Direttiva | Predefinito | Consigliato | Scopo |
|---|---|---|---|
proxy_connect_timeout |
60s | 5-10s | Tempo per stabilire la connessione al backend. Tienilo basso per fallire velocemente. |
proxy_read_timeout |
60s | 60-300s | Tempo di attesa per la risposta del backend. Aumenta per API lente. |
proxy_send_timeout |
60s | 60s | Tempo per inviare il corpo della richiesta al backend. Aumenta per upload grandi. |
location / {
proxy_pass http://127.0.0.1:3000;
proxy_connect_timeout 10s;
proxy_read_timeout 120s;
proxy_send_timeout 60s;
}
Questi timeout si applicano tra due operazioni di lettura/scrittura successive, non all'intera richiesta. Una risposta che invia dati ogni 30 secondi non fa mai scattare un proxy_read_timeout di 60 secondi.
Controlli del buffering
| Direttiva | Predefinito | Descrizione |
|---|---|---|
proxy_buffering |
on | Accumula l'intera risposta del backend prima di inviarla al client |
proxy_buffer_size |
4k o 8k | Buffer per la prima parte della risposta (header) |
proxy_buffers |
8 4k o 8 8k | Numero e dimensione dei buffer per il corpo della risposta |
proxy_busy_buffers_size |
8k o 16k | Dimensione massima dei buffer che possono essere occupati nell'invio al client |
Quando disabilitare il buffering del proxy?
Disabilita il buffering quando il backend invia dati in streaming: inferenza LLM (Ollama, vLLM), Server-Sent Events, risposte lunghe di tipo WebSocket o qualsiasi API che invia dati chunked in modo incrementale.
location /stream/ {
proxy_pass http://127.0.0.1:8080;
proxy_buffering off;
}
Mantieni il buffering abilitato per le API standard richiesta/risposta. Il buffering protegge il tuo backend dai client lenti: Nginx assorbe la risposta rapidamente e la invia al client al ritmo del client. Senza buffering, un client lento tiene aperta una connessione backend.
Per il tuning avanzato delle prestazioni, vedi .
Come risolvere gli errori 502 e 504?
502 Bad Gateway significa che Nginx non è riuscito a connettersi al backend o il backend ha inviato una risposta non valida. 504 Gateway Timeout significa che il backend non ha risposto entro il proxy_read_timeout.
Checklist 502 Bad Gateway
- Il backend è in esecuzione?
ss -tlnp | grep 3000
Se non c'è output, il backend non è in ascolto sulla porta 3000. Avvialo.
-
L'URL di proxy_pass è corretto? Controlla errori di battitura nel numero di porta o nell'indirizzo IP. Errore frequente: usare
https://per un backend che parla solo HTTP. -
SELinux blocca la connessione? (RHEL/CentOS)
sudo setsebool -P httpd_can_network_connect 1
- Controlla il log degli errori Nginx:
sudo tail -20 /var/log/nginx/error.log
Cerca connect() failed (111: Connection refused) o no live upstreams.
Checklist 504 Gateway Timeout
- Aumenta proxy_read_timeout:
proxy_read_timeout 300s;
- Controlla se il backend è lento:
time curl http://127.0.0.1:3000/slow-endpoint
Se impiega più del tuo proxy_read_timeout, aumenta il timeout o ottimizza il backend.
- Controlla lo stato degli upstream: se usi blocchi upstream, tutti i server potrebbero essere segnati come falliti. Controlla il log degli errori per
no live upstreams while connecting to upstream.
Leggere i log di Nginx
Nginx scrive i dettagli degli errori in /var/log/nginx/error.log. Per il monitoraggio in tempo reale:
sudo journalctl -u nginx -f
O monitora il log degli errori direttamente:
sudo tail -f /var/log/nginx/error.log
Per log separati per sito, aggiungi le direttive access_log e error_log al tuo server block:
server {
listen 80;
server_name app.example.com;
access_log /var/log/nginx/app-access.log;
error_log /var/log/nginx/app-error.log;
location / {
proxy_pass http://127.0.0.1:3000;
}
}
Questo separa i log per applicazione, rendendo più facile il debug di problemi con un backend specifico.
Problemi frequenti con gli header
Se il tuo backend riceve 127.0.0.1 come IP del client invece dell'indirizzo reale, la direttiva proxy_set_header X-Real-IP $remote_addr manca. Se la tua applicazione genera link HTTP usando http:// quando dovrebbe usare https://, l'header X-Forwarded-Proto non viene inoltrato. Alcuni framework (Express, Django, Rails) hanno bisogno di una configurazione esplicita per fidarsi degli header proxy. In Express:
app.set('trust proxy', 1);
In Django, imposta SECURE_PROXY_SSL_HEADER:
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
Senza queste impostazioni, la tua applicazione ignora gli header inoltrati anche quando Nginx li invia correttamente.
Riferimento completo: direttive proxy
Per un riferimento rapido, ecco il set minimo di direttive per diversi scenari di proxy:
# Standard HTTP proxy
location / {
proxy_pass http://127.0.0.1:3000;
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;
}
# WebSocket proxy
location /ws/ {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 3600s;
}
# Streaming proxy (SSE, LLM)
location /stream/ {
proxy_pass http://127.0.0.1:11434;
proxy_http_version 1.1;
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 600s;
}
Per i server block con più domini, vedi Nginx server blocks. Per l'hardening della sicurezza inclusi rate limiting e controllo degli accessi, vedi .
Copyright 2026 Virtua.Cloud. Tutti i diritti riservati. Questo contenuto è un'opera originale del team Virtua.Cloud. La riproduzione, ripubblicazione o redistribuzione senza autorizzazione scritta è vietata.
Pronto a provare?
Distribuisci il tuo server in pochi secondi. Linux, Windows o FreeBSD.
Vedi piani VPS