Configurazione del Proxy Inverso Nginx: Instradare il Traffico in Modo Efficiente

Configura un proxy inverso Nginx con routing chiaro, intestazioni corrette, supporto WebSocket, timeout, buffering e passaggi per la risoluzione dei problemi.

Configurazione del Proxy Inverso Nginx: Instradare il Traffico in Modo Efficiente

Una configurazione di proxy inverso Nginx permette a Nginx di ricevere traffico web pubblico e inoltrarlo a una o più applicazioni backend. Questo è utile quando la tua app gira su Node.js, Python, Go, Java o un altro servizio che non dovrebbe essere esposto direttamente a internet.

Invece di connetterti direttamente alla porta dell'app, gli utenti si connettono a Nginx sulle porte HTTP o HTTPS standard. Nginx gestisce il bordo pubblico, poi indirizza il traffico in modo efficiente al servizio interno corretto.

Cosa Fa un Proxy Inverso

Un proxy inverso si trova davanti ai tuoi server applicativi. Il client parla con Nginx, e Nginx parla con il backend. Per il browser, Nginx è il sito web. Per l'app, Nginx è il client upstream a meno che non si passino intestazioni che preservano i dettagli originali della richiesta.

Questo schema offre diversi vantaggi:

  • Puoi eseguire app su porte private come 3000, 5000 o 8080.
  • Puoi terminare TLS su Nginx.
  • Puoi instradare diversi nomi host o percorsi a servizi diversi.
  • Puoi aggiungere buffering, timeout, compressione e caching.
  • Puoi nascondere i dettagli implementativi del backend dalla rete pubblica.

Un proxy inverso di base per un'app in esecuzione su 127.0.0.1:3000 si presenta così:

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;
    }
}

La direttiva proxy_pass dice a Nginx dove inviare la richiesta. Le righe proxy_set_header preservano il contesto utile della richiesta. Senza di esse, la tua app potrebbe registrare ogni richiesta come proveniente da Nginx e potrebbe non sapere se la richiesta originale usava HTTP o HTTPS.

Se sei nuovo alla struttura degli host virtuali, consulta blocchi server Nginx prima di suddividere il traffico su più domini.

Instradare il Traffico per Host o Percorso

Le regole del proxy inverso di solito instradano per nome host, percorso o entrambi. Il routing basato su host è comune quando app separate usano domini diversi:

server {
    listen 80;
    server_name api.example.com;

    location / {
        proxy_pass http://127.0.0.1:4000;
        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;
    }
}

Il routing basato su percorso è utile quando un singolo dominio fronta più servizi:

server {
    listen 80;
    server_name example.com;

    location /api/ {
        proxy_pass http://127.0.0.1:4000/;
    }

    location / {
        proxy_pass http://127.0.0.1:3000;
    }
}

Fai attenzione agli slash finali in proxy_pass. In Nginx, proxy_pass http://backend; e proxy_pass http://backend/; possono riscrivere l'URI inoltrato in modo diverso quando usati all'interno di un blocco location. Testa i percorsi URL esatti che la tua app si aspetta.

Ad esempio, se /api/users raggiunge inaspettatamente il tuo backend come /users o /api/api/users, controlla prima la combinazione del prefisso location e dello slash finale. Questo è uno degli errori più comuni del proxy inverso.

Intestazioni, Timeout e WebSocket

Le intestazioni rendono il backend consapevole della richiesta originale. L'intestazione Host è importante quando l'app costruisce URL assoluti, convalida host consentiti o supporta più tenant. X-Forwarded-For aiuta a preservare l'IP client originale. X-Forwarded-Proto aiuta le app a generare link sicuri dopo la terminazione TLS.

Se il tuo backend usa WebSocket, aggiungi le intestazioni di upgrade:

location /socket/ {
    proxy_pass http://127.0.0.1:3000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
}

I timeout dovrebbero corrispondere al comportamento della tua applicazione. Una normale richiesta web dovrebbe terminare rapidamente. Un'esportazione di report, un endpoint di streaming o una richiesta di long polling potrebbero richiedere più tempo:

proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;

Evita di impostare timeout enormi ovunque solo per nascondere un endpoint lento. Timeout lunghi possono impegnare risorse e rendere più difficile notare reali interruzioni. Regola la location che ne ha bisogno.

Il buffering è un'altra impostazione importante. Per impostazione predefinita, Nginx può bufferizzare le risposte upstream prima di inviarle al client. Questo è utile per molte app web, ma gli endpoint di streaming potrebbero aver bisogno del buffering disabilitato:

proxy_buffering off;

Usalo solo dove è richiesto un comportamento di streaming. Per risposte HTML e API standard, il buffering spesso migliora la stabilità.

Terminazione TLS e Reindirizzamenti HTTPS

In molte configurazioni, Nginx gestisce anche HTTPS. Questo permette all'app backend di girare su una porta HTTP privata mentre gli utenti ottengono un sito sicuro normale sulla porta 443.

Una forma comune si presenta così:

server {
    listen 80;
    server_name example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    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;
    }
}

Il blocco server di reindirizzamento è intenzionalmente piccolo. Fa un solo lavoro: spostare il traffico HTTP semplice su HTTPS. Il blocco server HTTPS gestisce il proxy.

Se la tua app si trova dietro Nginx e genera ancora link http://, controlla se si fida di X-Forwarded-Proto. Molti framework necessitano di un'impostazione come "trust proxy" o un elenco di proxy consentiti prima di utilizzare le intestazioni inoltrate. Non fidarti ciecamente delle intestazioni inoltrate da internet pubblico a livello applicativo; assicurati che solo Nginx possa raggiungere la porta dell'app.

Gruppi Upstream e Bilanciamento del Carico Semplice

Quando un backend non è sufficiente, definisci un gruppo upstream:

upstream app_backend {
    server 10.0.1.10:3000;
    server 10.0.1.11:3000;
    keepalive 32;
}

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://app_backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        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;
    }
}

Nginx open source usa il bilanciamento del carico round-robin per impostazione predefinita. Puoi anche usare opzioni come least_conn quando richieste lunghe rendono un backend più occupato degli altri. Il controllo dello stato in Nginx open source è per lo più passivo: se un backend fallisce, Nginx può contrassegnarlo come non disponibile per un periodo basato sulle impostazioni di fallimento. Nginx Plus ha controlli di salute attivi, ma non dare per scontato che queste funzionalità esistano in ogni installazione.

Keepalive nel blocco upstream mantiene le connessioni backend aperte per il riutilizzo. Questo aiuta con molte richieste piccole, ma il backend deve essere in grado di gestire il numero di connessioni inattive e attive che Nginx può mantenere.

Contenitori e Reti Private

La configurazione del proxy inverso spesso diventa confusa in Docker o Kubernetes perché localhost cambia significato. Se Nginx è in esecuzione all'interno di un contenitore, 127.0.0.1:3000 punta al contenitore Nginx stesso, non a un contenitore app separato.

In Docker Compose, fai il proxy al nome del servizio:

location / {
    proxy_pass http://app:3000;
}

In Kubernetes, di solito fai il proxy a un nome DNS del Service, anche se molte distribuzioni Kubernetes usano un Ingress controller invece di blocchi server Nginx scritti a mano.

La regola semplice è questa: testa la connettività da dove gira Nginx, non dal tuo laptop e non dal contenitore backend. Se questo fallisce, anche Nginx fallirà:

curl -v http://app:3000/

Eseguilo all'interno del contenitore Nginx o sull'host Nginx, a seconda della tua distribuzione.

Confini di Sicurezza da Controllare

Un proxy inverso dovrebbe ridurre l'esposizione pubblica, non crearne accidentalmente di più. L'app backend dovrebbe normalmente ascoltare su un'interfaccia privata, una subnet privata o una rete di contenitori. Se la tua app ascolta su 0.0.0.0:3000 su una VM pubblica, gli utenti potrebbero bypassare Nginx completamente visitando http://example.com:3000.

Controlla le porte in ascolto sull'host:

sudo ss -ltnp

Se il backend deve ascoltare su tutte le interfacce all'interno di un contenitore, usa regole firewall, gruppi di sicurezza o impostazioni di rete del contenitore in modo che solo Nginx possa raggiungerlo dall'esterno. Questo è importante perché le app spesso si affidano a Nginx per TLS, limiti di dimensione delle richieste, limiti di frequenza, gateway di autenticazione o elenchi di indirizzi IP consentiti.

Fai anche attenzione alle intestazioni inoltrate. Intestazioni come X-Forwarded-For sono facili da falsificare per i client a meno che Nginx non le sovrascriva e l'app si fidi solo del proxy. Lo schema comune di Nginx è:

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

Questo aggiunge l'indirizzo del client alla catena. La tua applicazione o pipeline di logging dovrebbe sapere quali indirizzi proxy sono attendibili. Altrimenti, la limitazione della frequenza o i log di audit potrebbero registrare l'IP "client" sbagliato.

Anche i limiti di dimensione delle richieste fanno parte di questa discussione. Se la tua app accetta caricamenti di file, imposta client_max_body_size intenzionalmente:

client_max_body_size 25m;

Non aumentarlo globalmente a un valore enorme a meno che ogni route non ne abbia bisogno. Un endpoint per il caricamento di foto profilo e un endpoint JSON di login non dovrebbero avere lo stesso limite di corpo della richiesta.

Una Checklist Pratica per il Deployment

Prima di considerare il proxy inverso completato, testalo come un utente e come un operatore:

  • curl -I http://example.com/ dovrebbe mostrare il reindirizzamento o la risposta prevista.
  • curl -I https://example.com/ dovrebbe mostrare lo stato e le intestazioni previste.
  • I log dell'app dovrebbero mostrare l'host originale e un IP client utile.
  • Gli endpoint WebSocket o streaming dovrebbero essere testati separatamente.
  • Un percorso sbagliato, come /api/does-not-exist, dovrebbe fallire nel modo in cui la tua app si aspetta.
  • I log degli errori di Nginx dovrebbero essere silenziosi durante le richieste normali.

Per il routing dei percorsi, mi piace testare tre URL per ogni location: il prefisso nudo, un percorso annidato normale e un percorso con una stringa di query. Ad esempio:

curl -i http://example.com/api/
curl -i http://example.com/api/users
curl -i 'http://example.com/api/users?page=2'

Questi semplici controlli catturano molti errori di slash finale prima che lo facciano gli utenti.

Quando ricarichi, usa la stessa sequenza sicura ogni volta:

sudo nginx -t
sudo systemctl reload nginx
sudo tail -n 50 /var/log/nginx/error.log

Se l'app è dietro la terminazione TLS, verifica anche che i link generati, i reindirizzamenti, i cookie e gli URL di callback usino HTTPS. I flussi di login sono dove questo spesso si rompe per primo, perché i reindirizzamenti e i cookie sicuri dipendono dalla comprensione dello schema originale da parte dell'app.

Schemi di Fallimento Comuni

502 Bad Gateway di solito significa che Nginx ha raggiunto la posizione del proxy inverso ma non ha potuto ottenere una risposta valida dall'upstream. Il backend potrebbe essere giù, la porta potrebbe essere sbagliata, l'app potrebbe ascoltare su un'interfaccia diversa o la connessione potrebbe essere rifiutata da un firewall.

504 Gateway Timeout di solito significa che Nginx si è connesso a qualcosa ma non ha ricevuto una risposta in tempo. Può essere un'app lenta, una query di database bloccata, un pool di worker sovraccarico o un timeout troppo breve per l'endpoint. Aumentare proxy_read_timeout può essere appropriato per un endpoint di esportazione noto per essere lungo. Non è una soluzione per un'app generalmente lenta.

I loop di reindirizzamento spesso derivano da una discrepanza tra la terminazione TLS e le impostazioni di fiducia dell'applicazione. Il browser raggiunge Nginx su HTTPS, Nginx fa il proxy all'app su HTTP, e l'app pensa che la richiesta originale fosse HTTP semplice. L'app reindirizza a HTTPS, ma la stessa cosa accade di nuovo. Passare X-Forwarded-Proto è solo metà della soluzione; l'app deve anche fidarsene dal proxy.

Gli IP client mancanti di solito si presentano come ogni richiesta proveniente da 127.0.0.1, un indirizzo bridge Docker o un indirizzo di bilanciatore del carico privato. Passa X-Real-IP e X-Forwarded-For, poi configura l'applicazione e il livello di logging per leggerli in modo sicuro.

Asset statici danneggiati dopo il routing dei percorsi spesso derivano da app che presumono di vivere su /. Se monti un'app sotto /admin/, potrebbe ancora generare link a /assets/app.css. A volte puoi risolvere questo con le impostazioni del percorso base dell'app. Cercare di riscrivere ogni percorso di asset in Nginx è di solito fragile.

Un Piccolo Esempio del Mondo Reale

Immagina una VM che esegue tre servizi:

  • Un sito di marketing su 127.0.0.1:3000
  • Un'API su 127.0.0.1:4000
  • Uno strumento di amministrazione su 127.0.0.1:5000

Potresti instradarli così:

server {
    listen 443 ssl;
    server_name example.com;

    location /api/ {
        proxy_pass http://127.0.0.1:4000/;
        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;
    }

    location /admin/ {
        proxy_pass http://127.0.0.1:5000/;
        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;
    }

    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;
    }
}

Questo può funzionare, ma ha dei compromessi. L'API e l'app di amministrazione devono entrambe comportarsi correttamente sotto i loro prefissi. Se non lo fanno, nomi host separati come api.example.com e admin.example.com potrebbero essere più puliti. Una buona progettazione del proxy inverso non riguarda solo il far accettare la configurazione a Nginx; riguarda la scelta di un routing con cui le tue applicazioni possano convivere.

Testare e Risolvere i Problemi della Configurazione

Testa sempre la configurazione prima di ricaricare:

nginx -t

Poi ricarica Nginx e fai una richiesta attraverso il nome host pubblico. Controlla sia il browser che i log. I log di accesso di Nginx mostrano se la richiesta ha raggiunto Nginx. I log degli errori mostrano fallimenti di connessione, timeout upstream e dettagli di bad gateway.

Un esempio pratico: la tua app Node.js gira bene su curl http://127.0.0.1:3000, ma il sito pubblico mostra 502 Bad Gateway. Questo significa che Nginx è raggiungibile, ma non può parlare con successo con l'upstream. Controlla se l'app sta ascoltando sull'indirizzo previsto, se la porta è corretta e se un firewall locale blocca la connessione.

I problemi comuni del proxy inverso includono:

  • Porta o indirizzo upstream sbagliato.
  • Backend vincolato a localhost quando Nginx gira in un altro contenitore.
  • Intestazioni di upgrade WebSocket mancanti.
  • App che rifiuta richieste perché l'intestazione Host è inaspettata.
  • Riscrittura URI errata causata da uno slash finale.
  • Timeout troppo brevi per un endpoint lento.

Per fallimenti upstream più profondi, usa Risoluzione dei problemi Nginx 502.

Quando Chiedere Aiuto

Chiedi aiuto a un ingegnere DevOps se il proxy inverso si estende su più contenitori, reti private, certificati TLS o upstream con bilanciamento del carico. Queste configurazioni possono fallire in modi che sembrano problemi di Nginx ma sono in realtà problemi di DNS, firewall, rete di contenitori o salute dell'applicazione.

Dovresti anche chiedere aiuto prima di esporre pannelli di amministrazione, API interne o servizi di staging attraverso un proxy inverso pubblico. Piccoli errori di routing possono creare seri problemi di accesso.

Una configurazione di proxy inverso Nginx è uno degli schemi più utili nell'infrastruttura web. Mantieni il routing chiaro, passa le intestazioni giuste, testa attentamente il comportamento dei percorsi e lascia che Nginx sia il punto di ingresso pubblico stabile per i tuoi servizi backend.