Dans cet article, nous allons voir comment décharger le traitement du SSL sur Apache. Les requêtes HTTPS seront traités en amont puis passées vers apache sur le port non SSL.
Le but est d’avoir un serveur Nginx en reverse Proxy qui se place devant apache, traite la partie SSL et renvoi le trafic vers le serveur web Apache (en non SSL). Cela permet de protéger Apache contre les pics de trafic, Nginx étant plus résistant sur ce point là et bénéficiant nativement de règles pour mettre en place des limites. Il est également possible d’ajouter du cache sur Nginx mais ça ne sera pas le sujet de cet article, on n’évoquera que la partie SSL / Offloading.
Il y a quelques subtilités à connaitre pour faire cela sinon cela risque de générer des erreurs (400 Bad Request) et des boucles de redirections. Nous allons également voir pourquoi cela n’est pas totalement transparent.
Table des matières
Installation et configuration d’apache
La première étape est d’installer un serveur web apache puis de configurer un vhost pour le site internet qui nous intéresse. Dans mon cas, je travaille sur Centos donc l’installation se passe avec Yum.
On va également installer PHP, prendre une version assez récente depuis les dépôts de Remi. L’implémentation de PHP choisi sera PHP-FPM, le PHP sera exécuté avec l’utilisateur système qui sera utilisé pour l’hébergement du site internet.
L’hôte virtuel d’apache est assez classique, il y a juste quelques subtilités :
- On écoute sur le port 8080 car le reverse proxy Nginx sera sur les ports 80 et 443. Il faudra bien penser à modifier cela dans le httpd.conf également.
- Il y a un ProxyPassMatch pour php-fpm, rien d’inhabituel. La configuration PHP-FPM n’est pas l’objet de cet article mais si cela vous intéresse, c’est là.
- On créé une variable d’environnement HTTPS défini à On, s’il y a un header X-Forwaded-Proto. Ce dernier sera généré par Nginx, c’est utile pour les scripts PHP. Certains scripts PHP se base la dessus pour déterminer si l’environnement est https ou non.
Il reste une configuration supplémentaire à faire dans Apache, pour qu’il récupère l’adresse IP du client qui est passé par Nginx dans un header X-Forwarded-For. Sans cette configuration supplémentaire, Apache verrait simplement l’adresse IP de Nginx et non celle du vrai client. Cela doit être ajouté dans le dossier conf.modules.d.
Si la bonne adresse IP n’apparaît dans les logs, cela peut venir de trois choses :
- RemoteIPTrustedProxy n’autorise pas l’adresse IP sur laquelle se trouve le reverse proxy Nginx
- RemoteIPHeader ne correspond pas au hearder envoyé par Nginx, parfois c’est X-Real-Ip
- Dans le httpd.conf il faut changer le format des logs pour remplacer le %h par %a
Installation du reverse proxy Nginx
Maintenant, il faut procéder à l’installation puis à la configuration de Nginx. La configuration sera assez simple, il faudra simplement définir les fichiers du certificats SSL, définir quelques headers supplémentaires puis faire un proxy_pass sur le port d’écoute du serveur apache en arrière plan.
Habituellement, je compile Nginx manuellement pour avoir la dernière version et y intégrer des extensions supplémentaires mais dans le cas présent, on se contentera d’une installation depuis les dépôts.
Pour la configuration, c’est assez basique, la petite subtilité est dans l’ajout de header supplémentaire :
Pourquoi ce n’est pas totalement transparent au final ?
Maintenant, voyons pourquoi cela n’est pas totalement transparent. Pour plusieurs raisons, premier exemple, le code ci-dessous va générer une boucle de redirection alors qu’il est tout à fait valide lorsque cela passe par le « circuit SSL » d’apache :
C’est curieux, pourtant la variable HTTPS devrait être à « On » grâce à la ligne SetEnvIf dans la définition du virtualhost ? Ce n’est pas le cas :
- La ligne SetEnvIf dans la configuration d’apache définie une variable d’environnement qui va être passée à PHP plus tard
- Dans le RewriteCond, la variable HTTPS est une variable interne à apache. Ce n’est pas la même chose que la variable d’environnement, même si cela porte le même nom. C’est expliquée dans la documentation de la directive RewriteCond d’apache : la variable peut être utilisée sans problème, que mod_ssl soit chargé ou non. À ma connaissance, il n’est pas possible de modifier cette variable là dynamiquement, via des configurations.
Il y a également d’autres effets de bord, certaines variables apaches sont différentes et des variables d’environnements n’existent pas forcément, c’est le cas des variables suivantes :
- SERVER_PORT contiendra la valeur 8080, parfois PHP ou même des directives .htaccess se base la dessus (exemple : forcer le HTTPS si le port utilisé est le 80)
- REQUEST_SCHEME contiendra http alors qu’en réalité c’est HTTPS
Pour ces raisons, les règles de redirections ou même le code de l’application doivent également prendre en compte des headers supplémentaires et standard (cf. la documentation des headers de Mozilla à ce sujet), ajoutés par Nginx :
- X-Forwarded-Proto : qui permet de passer le bon protocole car request_scheme sera dans certains cas invalide
- X-Forwarded-For : qui contiendra l’adresse IP du client à l’origine de la requête
- X-Forwarded-Port : qui contiendra le port d’origine d’écoute
Pour reprendre le premier exemple de .htaccess, voici un exemple corrigé qui fonctionne à tous les coups :
Conclusion
Plusieurs conclusions à tirer de cette expérience.
Ce n’est pas forcément possible d’Offloader totalement le SSL dans le cas ou on ne maîtrise pas les applications hébergés. Par exemple : dans un contexte d’hébergement mutualisé. Certaines règles de redirections dans les .htaccess ne penseront pas à vérifier si le site est derrière un reverse proxy. C’est la même chose dans le code des applications hébergés.
Dans le cas ou on ne maîtrise pas les applications finales hébergées, c’est mieux de ne pas décharger le SSL. Pour cela il faut faire un proxy_pass sur le port SSL d’apache. Ainsi les variables d’apache et les variables d’environnement seront correctement définies.
Si vous développez des applications, il faut penser à cela. Par exemple en vérifiant les headers X-Forwarded-* pour que l’application soit déployable plus facilement.