Apache Reverse Proxy: configurazione, TLS, Real IP e sicurezza

Guida pratica ad Apache come reverse proxy: mod_proxy, TLS, backend, WebSocket, header, Real IP dietro proxy/CDN, logging, test e hardening.

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.

Riferimenti

Apache mod_proxy · Apache mod_remoteip.