Reverse proxy avec Apache httpd et mod_proxy

Toutes les informations consignées ci-dessous ont été testées avec Apache 2.4.52 et Tomcat 11.

Apache mod_proxy

Le module proxy peut fonctionner en forward ou reverse. C’est cette seconde fonctionnalité qui nous intéresse ici.

Il permet de paramétrer des transferts de requêtes entre Apache et d’autres serveurs Web, comme Tomcat ou WildFly. Cependant, ce module ne prend pas en charge le transport pour lequel on doit associer des modules de transport : proxy_http, proxy_ajp,…​

mod_proxy sous Debian

Sous Debian, l’activation du module proxy et de ses modules de transport passe par la création de liens symbolique dans le répertoire mods-enabled de la configuration d’Apache : fichiers .load pour le chargement des modules et fichiers .conf pour leur configuration.

$> ln -s /etc/apache2/mods-available/proxy.conf /etc/apache2/mods-enabled/
$> ln -s /etc/apache2/mods-available/proxy.load /etc/apache2/mods-enabled/
$> ln -s /etc/apache2/mods-available/proxy_http.load /etc/apache2/mods-enabled/

L’activation des modules peut aussi se faire avec a2enmod, qui crée les liens symboliques. La désactivation se fait avec a2dismod, qui supprime les liens symboliques, sans supprimer les fichiers.

$> a2enmod proxy
$> a2enmod proxy_http

Pour finir, les modifications seront prises en compte après le redémarrage d’Apache.

$> systemctl restart apache2
# ou
$> apache2ctl restart

Configuration en HTTP

Dans cet exemple, j’ai exposé via Apache une application app déployée dans Tomcat. Tomcat et Apache sont sur le même serveur.

Le premier niveau de configuration est d’indiquer que les requêtes du contexte /manager doivent être passées à Tomcat. L’URL dans la directive ProxyPass est sur le protocole http, ce qui nécessite le chargement du module proxy_http.

Le paramétrage se fait dans le fichier /etc/apache2/mods-enabled/proxy.conf.

# mods-available/proxy.conf
<Location /app>
    ProxyPass http://localhost:8080/app
</Location>

Le problème dans cette configuration, c’est que les redirections sont mal traitées. Si l’application fait des redirections avec des redirections, elle le fait avec les informations dont elle dispose, c’est-à-dire celle de Tomcat. Apache doit donc réécrire ces URLs, grâce à la directive ProxyPassReverse. Et pour que Apache conserve le header Host, on peut ajouter la directive ProxyPreserveHost.

# mods-available/proxy.conf
ProxyPreserveHost on
<Location /app>
    ProxyPass http://localhost:8080/app
    ProxyPassReverse http://localhost:8080/app
</Location>

Enfin, pour que Tomcat ait les bonnes informations pour ses logs d’accès, il faut ajouter une RemoteIpValve qui transfert l’addresse du client du header X-Forwarded-For vers le champs RemoteAddr de l’API servlet.

<!-- conf/server.xml -->
<Valve className="org.apache.catalina.valves.RemoteIpValve" />
<Valve className="org.apache.catalina.valves.AccessLogValve"
       ...
       requestAttributesEnabled="true" />

Configuration avec TLS

Si on n’a pas confiance dans le réseau entre Apache et Tomcat, on peut établir une connexion TLS. Pour ça, il faut que le module ssl soit actif. Puis, on active le moteur SSL pour le module proxy avec SSLProxyEngine.

Si le certificat de Tomcat n’est pas valide, on peut désactiver les vérifications.

Enfin, on modifie l’adresse du ProxyPass pour y mettre une URL en https.

# mods-available/proxy.conf
SSLProxyEngine on

SSLProxyVerify none
SSLProxyCheckPeerCN off
SSLProxyCheckPeerName off

<Location /app>
    ProxyPass https://localhost:8443/app
    ProxyPassReverse https://localhost:8443/app
</Location>

Configuration en HTTP/2

Le protocole HTTP/2 est pris en charge par le module proxy_http2.

$> a2enmod proxy_ajp

Il supporte évidemment HTTP/2 sur TLS.

# mods-available/proxy.conf
SSLProxyEngine on
<Location /app>
    ProxyPass h2://localhost:8443/app
    ProxyPassReverse https://localhost:8443/app
</Location>

Pour ça, le connecteur 8443 doit supporter l’upgrade vers HTTP/2.

<!-- conf/server.xml -->
<Connector port="8443" protocole="HTTP/1.1" >
    <UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" />
    ...
</Connector>

Il supporte aussi HTTP/2 clear text.

# mods-available/proxy.conf
<Location /app>
    ProxyPass h2c://localhost:8080/app
    ProxyPassReverse http://localhost:8080/app
</Location>

Là aussi, le connecteur doit supporter l’upgrade vers HTTP/2.

<!-- conf/server.xml -->
<Connector port="8080" protocole="HTTP/1.1">
    <UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" />
    ...
</Connector>

Configuration en AJP

Avec AJP, il faut un connecteur qui supporte ce protocole dans Tomcat.

<!-- conf/server.xml -->
<Connector protocol="AJP/1.3"
           port="8009" redirectPort="443"
           secret="5ecr3t"/>

Le secret peut être omis si la communication se fait dans un réseau sûr. Dans cas, il faut mettre l’attribut secretRequired="false".

Du coté d’Apache, le module de transport proxy_ajp doit être activé.

$> a2enmod proxy_ajp

Puis ProxyPass renvoie vers le port 8009, avec le schema ajp, à la place de 8080 et http. Pour ce protocole, il n’y a pas besoin de la directive ProxyPreserveHost.

# mods-available/proxy.conf
<Location /docs>
    ProxyPass ajp://localhost:8009/app secret="5ecr3t"
    ProxyPassReverse ajp://localhost:8009/app
</Location>

Prise en charge des ressources statiques

Pour que les ressources statiques soient prises en charge directement par Apache, il faut les placer dans le répertoire /var/www/html et configurer le module proxy pour qu’il ne transfère pas les requêtes à Tomcat.

# mods-available/proxy.conf
ProxyPreserveHost on
<Location /app>
    ProxyPass http://localhost:8080/app
    ProxyPassReverse http://localhost:8080/app
</Location>

<Location /app/css>
    ProxyPass !
</Location>
<Location /app/img>
    ProxyPass !
</Location>

Ça peut aussi se faire, avec plus de souplesse, avec le moteur de réécriture.

RewriteEngine On

# Ressource statique interceptée
RewriteRule "^/app/css/?(.*)" "$0" [L]
RewriteRule "^/app/js/?(.*)" "$0" [L]
RewriteRule "^/app/images/?(.*)" "$0" [L]

# Le reste est envoyé au mod_proxy [P]
RewriteRule "^/app/(.*)" "http://localhost:8080/app/$1" [P]

<Location /app>
  ProxyPassReverse http://localhost:8080/app
</Location>