Quando apriamo un sito web nel browser sembra che il client stia parlando direttamente con il server che esegue l’applicazione. In molte infrastrutture moderne non è così: davanti all’applicazione esiste un server che riceve tutte le richieste pubbliche e decide a quale backend inoltrarle. Quel server è il reverse proxy.
Il termine “reverse” serve a distinguerlo dal proxy tradizionale usato dai client per uscire verso Internet. Nel forward proxy il client sa di usare un intermediario; nel reverse proxy il client vede semplicemente app.example.net e non ha bisogno di sapere se dietro esista un singolo processo PHP, dieci container o più server applicativi.
Perché mettere un server davanti ai server?
Aggiungere un livello sembra controintuitivo, ma centralizza funzioni che altrimenti dovremmo replicare su ogni backend. Il certificato TLS può essere gestito in un solo posto; i backend possono rimanere su reti private; i log possono avere un formato uniforme; possiamo applicare header di sicurezza, rate limiting e controlli applicativi prima che la richiesta raggiunga l’applicazione.
C’è anche un vantaggio operativo. Un backend può cambiare indirizzo o porta senza che l’utente lo sappia. Possiamo spostare un’applicazione da 172.16.0.200:8080 a un altro host aggiornando il proxy, mantenendo invariato l’URL pubblico.
Il reverse proxy diventa una frontiera di fiducia
Questa centralizzazione ha una conseguenza importante: il proxy diventa anche il punto in cui decidiamo quali informazioni sul client siano attendibili. Se esiste una CDN davanti ad Apache, il backend vede la CDN come peer TCP. Per ricostruire l’IP originale dobbiamo fidarci di un header, ma soltanto quando quell’header arriva da un proxy realmente autorizzato.
È lo stesso motivo per cui non basta configurare X-Forwarded-For e dimenticarsene. Un dato di sicurezza vale quanto la catena che lo ha prodotto. Questa considerazione diventa ancora più importante se l’indirizzo client alimenta Fail2Ban, rate limiting o RwF.
Con questo modello in mente passiamo alla configurazione Apache.
Scenario
Internet
|
| HTTPS :443
v
Apache Reverse Proxy
|
+----> 172.16.0.200:8080 applicazione
+----> 172.16.0.210:3000 servizio Node
+----> altri backend non esposti direttamente
Moduli necessari
a2enmod proxy
a2enmod proxy_http
a2enmod headers
a2enmod ssl
a2enmod remoteip
a2enmod http2
apache2ctl configtest
systemctl reload apache2
Per WebSocket, sulle versioni moderne di Apache mod_proxy_http gestisce l’upgrade; mod_proxy_wstunnel resta utile in configurazioni legacy o specifiche.
VirtualHost minimale
<VirtualHost *:443>
ServerName app.example.net
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/app.example.net/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/app.example.net/privkey.pem
Protocols h2 http/1.1
ProxyRequests Off
ProxyPreserveHost On
ProxyPass / http://172.16.0.200:8080/ connectiontimeout=5 timeout=60
ProxyPassReverse / http://172.16.0.200:8080/
ErrorLog ${APACHE_LOG_DIR}/app-error.log
CustomLog ${APACHE_LOG_DIR}/app-access.log combined
</VirtualHost>
ProxyRequests Off è fondamentale: stiamo costruendo un reverse proxy, non un forward proxy aperto. ProxyPassReverse riscrive gli header di redirect del backend così che il client continui a vedere l’URL pubblico.
Trailing slash: devono combaciare
Se il path locale termina con /, anche l’URL backend dovrebbe terminare con /. Una coppia incoerente è una delle cause classiche di URL composti male.
ProxyPass /api/ http://172.16.0.200:8080/api/
ProxyPassReverse /api/ http://172.16.0.200:8080/api/
WebSocket
ProxyPass /ws/ http://172.16.0.210:3000/ws/ upgrade=websocket
ProxyPassReverse /ws/ http://172.16.0.210:3000/ws/
Il supporto upgrade=websocket è disponibile nelle release moderne di Apache 2.4. Non va usato come regola universale sull’intero sito: limitiamolo al path che usa davvero WebSocket.
Header verso il backend
mod_proxy_http aggiunge header come X-Forwarded-For. Possiamo esplicitare anche il protocollo originale:
RequestHeader set X-Forwarded-Proto "https"
RequestHeader set X-Forwarded-Port "443"
Real IP dietro un proxy o una CDN
Il backend non deve fidarsi indiscriminatamente di X-Forwarded-For o di un header analogo. Gli header del client reale sono attendibili solo quando la connessione proviene da proxy conosciuti. Con mod_remoteip definiamo quindi sia l’header sia i proxy autorizzati.
RemoteIPHeader X-Forwarded-For
# Esempio: solo il nostro proxy frontale
RemoteIPTrustedProxy 172.16.0.50
Se davanti c’è una CDN, la lista delle reti fidate deve provenire dal provider e va mantenuta aggiornata. Fidarsi dell’header da qualunque sorgente consente al client di falsificare il proprio IP e può contaminare log, rate limit e sistemi di ban.
Log: IP logico e IP della connessione
Dopo mod_remoteip, %a rappresenta l’indirizzo client ricostruito mentre %{c}a conserva l’indirizzo della connessione reale. Registrare entrambi è molto utile durante un incidente.
LogFormat "%a via %{c}a %l %u %t \"%r\" %>s %b" proxy_combined
CustomLog ${APACHE_LOG_DIR}/app-access.log proxy_combined
Proteggere il backend
Il backend dovrebbe accettare traffico applicativo soltanto dal reverse proxy o dalle reti strettamente necessarie. Se 172.16.0.200:8080 è raggiungibile direttamente da Internet, molte delle policy applicate sul proxy possono essere aggirate.
Test
apache2ctl configtest
systemctl reload apache2
curl -I https://app.example.net/
curl -vk https://app.example.net/api/health
openssl s_client -connect app.example.net:443 -servername app.example.net </dev/null
Dal proxy controlliamo anche il backend direttamente, così separiamo problemi TLS/front-end da problemi applicativi:
curl -v http://172.16.0.200:8080/health
ss -lntp | grep -E ':80|:443'
journalctl -u apache2 -f
HTTP/2: niente più Server Push
Le vecchie configurazioni consigliavano HTTP/2 Server Push. Oggi non è più una ottimizzazione da proporre: i browser principali lo hanno abbandonato. Abilitiamo HTTP/2 quando utile con Protocols h2 http/1.1, ma non costruiamo la strategia di performance attorno a Push.
Dal reverse proxy al Reactive Web Firewall
Una volta centralizzato l’ingresso HTTP, il proxy diventa anche il punto migliore per riconoscere richieste applicative ostili. Nel nostro stack RwF aggiunge regole Apache che possono trasformare un match ad alta confidenza in un 403 e in un ban nftables temporaneo. È il passo successivo naturale rispetto al semplice reverse proxy.

