Cosa c’è tra il browser e la tua app
Punta il browser su example.com e la richiesta non tocca mai il processo che esegue il tuo codice. Incontra prima qualcos’altro: un server che legge la richiesta, decide quale backend deve rispondere, la inoltra lì e rimanda indietro la risposta come se l’avesse prodotta lui stesso. Quel server è un reverse proxy.
Nginx, Caddy, Traefik e HAProxy fanno tutti questo lavoro, così come il load balancer davanti a un cluster Kubernetes. La disposizione non cambia mai: i client parlano con il proxy e con nient’altro. I server reali restano dietro, in qualunque numero e ovunque girino.
Come un reverse proxy gestisce una richiesta
Un client apre una connessione verso l’IP e la porta del reverse proxy, come farebbe con un server qualsiasi. Il proxy termina quella connessione, legge la riga di richiesta e gli header, e li confronta con le proprie regole — di solito l’header Host e il percorso dell’URL. In base a quel confronto apre una seconda connessione, separata, verso un backend, inoltra la richiesta su quella, aspetta la risposta e la inoltra a sua volta sulla connessione originale del client.
Due connessioni indipendenti, cucite insieme. Dal lato client la cosa è indistinguibile dal parlare direttamente con il backend. Dal lato backend, ogni richiesta sembra arrivare da un’unica macchina, il proxy, invece che da internet in generale.
| |
Poiché il proxy vede ogni richiesta prima del backend, può ispezionarla, modificarla, metterla in cache o rifiutarla. È questa posizione a scaricargli addosso il lavoro che nessuno vuole dentro l’applicazione: terminare il TLS, comprimere le risposte, fare cache dei contenuti statici, limitare i client abusivi e mandare percorsi diversi a servizi diversi — /api a un backend, / a un altro, /static direttamente su disco.
Reverse proxy vs forward proxy
Entrambi stanno in mezzo a una connessione. La differenza è per chi lavorano.
| Forward proxy | Reverse proxy | |
|---|---|---|
| Agisce per conto di | Il client | Il server |
| Il client sa che esiste | Di solito sì (configurato esplicitamente) | No (sembra il server vero) |
| Cosa nasconde | L’identità del client al server | L’identità del server al client |
| Uso tipico | Filtraggio del traffico in uscita in azienda, bypass di geo-blocchi, navigazione anonima | Routing, terminazione TLS, cache, protezione dei server di origine |
Un forward proxy è la scatola che il tuo datore di lavoro mette in ufficio perché tutto il traffico in uscita passi da un unico punto filtrato: il sito che visiti registra l’indirizzo del proxy, non quello del tuo portatile. Un reverse proxy lo gestisce chi possiede il sito, e nasconde l’estremità opposta. Dall’esterno non puoi dire se example.com è un server o quaranta.
Reverse proxy vs load balancer
I due termini vengono usati come sinonimi, soprattutto perché un unico processo Nginx o HAProxy di solito fa entrambi i lavori insieme.
| Reverse proxy | Load balancer | |
|---|---|---|
| Domanda principale a cui risponde | Cosa deve succedere a questa richiesta? | Quale server deve gestire questa richiesta? |
| Funziona con un solo backend? | Sì, resta utile (TLS, cache, routing per percorso) | No, servono almeno due per bilanciare |
| Funzionalità extra tipiche | Cache, riscrittura degli header, terminazione TLS, routing per percorso | Health check, distribuzione pesata, affinità di sessione |
Un reverse proxy davanti a un solo backend si guadagna comunque il suo posto: termina il TLS così la tua app non deve farlo, nasconde l’indirizzo reale del backend, aggiunge gzip. Un load balancer non ha niente da fare finché non ci sono almeno due backend tra cui scegliere. L’etichetta che usi descrive la configurazione che hai scritto, non il software che hai installato.
Come configurare Nginx come reverse proxy
Ascolta su una porta, confronta un hostname, inoltra a un backend. È tutto il minimo indispensabile:
| |
proxy_pass è la riga che conta: tutto quello che location intercetta va a http://127.0.0.1:3000. Le quattro righe proxy_set_header esistono perché altrimenti il backend vede il proxy come client, e ogni richiesta sembra arrivare da 127.0.0.1. X-Real-IP e X-Forwarded-For portano l’indirizzo reale del client fino in fondo. X-Forwarded-Proto dice al backend se la richiesta originale era HTTPS, dato che il TLS è stato terminato al proxy e il salto successivo è HTTP semplice.
Un dettaglio manda in crisi le configurazioni di continuo: uno slash finale sull’URI di proxy_pass cambia il percorso inoltrato. proxy_pass http://backend; (senza percorso) inoltra l’URI originale senza modifiche. proxy_pass http://backend/; (con slash finale) toglie la parte di URI che location ha intercettato prima di inoltrare. Con location /api/ e proxy_pass http://backend/;, una richiesta a /api/users arriva al backend come /users. Sbaglialo al contrario e ogni rotta risponde 404 sul backend mentre curl contro il proxy sembra funzionare.
Reverse proxy davanti a un’app Dockerizzata
È qui che il pattern si vede più spesso nel lavoro di tutti i giorni: Nginx in un container, la tua app in un altro, entrambi sulla stessa rete Docker così che Nginx possa raggiungere l’app tramite il nome del container — il comportamento DNS spiegato in cosa offre una rete Docker.
| |
| |
app espone la porta 3000 sulla rete Compose ma non pubblica niente sull’host. Solo Nginx lo fa, sulla porta 80. Il nome app:3000 nella configurazione Nginx si risolve tramite il DNS interno di Docker, lo stesso meccanismo descritto nella guida a Docker Compose. Lancia docker compose up e le richieste a localhost arrivano su Nginx, che le inoltra nel container dell’app. L’app non è mai raggiungibile dall’esterno dell’host da sola, il che è tanto voluto quanto il routing stesso.
Quanto costa un reverse proxy
Aggiunge un salto di rete e un punto in più in cui una richiesta può fallire. Un proxy_pass configurato male o un header perso si presenta come un bug nell’app quando il difetto sta un livello più a monte. È anche un singolo punto di guasto per default: uccidi quell’unico processo Nginx e ogni backend dietro diventa irraggiungibile. Le configurazioni in produzione lo eseguono in modo ridondante, oppure passano il compito a qualcosa che già dà per scontato il guasto — un load balancer cloud, o un Ingress controller Kubernetes appoggiato ai Deployment e Pod sottostanti.
Un reverse proxy non rende nemmeno un’app più veloce o più scalabile di per sé. Può fare cache e comprimere, ma un backend lento resta lento; il proxy inoltra quella lentezza un salto più tardi. E non è autenticazione né validazione dell’input. Bloccare richieste palesemente malformate al bordo è un vantaggio in più, mai un motivo per cui l’app smetta di controllare quello che riceve.
Portare questa configurazione in produzione
Prendi il compose.yaml qui sopra, sostituisci app con un servizio reale, e aggiungi ssl_certificate e ssl_certificate_key al blocco server di Nginx una volta che hai un certificato. È questa la differenza tra un esercizio locale e qualcosa che metteresti davanti a traffico reale. Se instradi verso più di un backend, sostituisci l’indirizzo singolo con un blocco upstream che contiene più righe server e punta proxy_pass a quel blocco: la stessa configurazione diventa un load balancer.