Quelques jours après avoir installé Varnish comme reverse proxy devant Apache, je me suis aperçu que tous les commentaires du site étaient enregistrés avec l’adresse IP 127.0.0.1.
Même problème dans les logs Apache : au lieu de voir l’adresse IP réelle du visiteur, je voyais uniquement l’adresse du reverse proxy.
127.0.0.1 - - [14/May/2011:08:54:47 +0200] "GET / HTTP/1.1" 200 12345Code language: JavaScript (javascript)
C’est logique : Apache ne reçoit plus directement la requête du visiteur. Il reçoit la requête depuis Varnish. Pour Apache, le client immédiat est donc Varnish, souvent en 127.0.0.1, ::1 ou sur une IP privée.
Pour retrouver la véritable IP du visiteur, il faut que le reverse proxy transmette cette IP dans un header HTTP, puis qu’Apache fasse confiance à ce header uniquement lorsqu’il provient d’un proxy connu.
Pourquoi Apache voit l’adresse du reverse proxy
Dans une architecture classique sans reverse proxy, le visiteur se connecte directement à Apache :
Visiteur → Apache
Apache voit donc l’IP du visiteur.
Avec Varnish, l’architecture devient :
Visiteur → Varnish → Apache
Apache voit alors l’IP de Varnish, car c’est Varnish qui établit la connexion vers Apache.
La vraie adresse du visiteur doit donc être transmise dans un header, le plus souvent :
X-Forwarded-For: 203.0.113.42Code language: CSS (css)
Varnish documente justement l’usage de X-Forwarded-For pour transporter l’adresse IP du client initial vers le backend.
Ancienne méthode : mod_rpaf
À l’époque, la solution consistait à installer mod_rpaf, pour Reverse Proxy Add Forward :
apt-get install libapache2-mod-rpaf
a2enmod rpafCode language: JavaScript (javascript)
Puis à configurer le module ainsi :
RPAFenable On
RPAFsethostname On
RPAFproxy_ips 127.0.0.1
RPAFheader X-Forwarded-ForCode language: CSS (css)
Cette solution fonctionnait, mais elle n’est plus celle que j’utiliserais aujourd’hui sur Apache 2.4.
La méthode moderne consiste à utiliser le module officiel mod_remoteip.
Solution moderne : utiliser mod_remoteip
mod_remoteip permet à Apache de remplacer l’adresse IP apparente du client par l’adresse fournie dans un header comme X-Forwarded-For, à condition que la requête provienne d’un proxy déclaré comme fiable.
La documentation Apache explique que le module remplace l’adresse IP originale du client par l’adresse utilisateur présentée dans un header de requête, configuré avec RemoteIPHeader.
On active d’abord le module :
sudo a2enmod remoteip
On crée ensuite un fichier de configuration dédié :
sudo nano /etc/apache2/conf-available/remoteip.conf
Pour un Varnish local, ajoutez :
RemoteIPHeader X-Forwarded-For
RemoteIPTrustedProxy 127.0.0.1
RemoteIPTrustedProxy ::1Code language: CSS (css)
Activez ensuite la configuration :
sudo a2enconf remoteip
Testez Apache :
sudo apachectl configtest
Puis rechargez Apache :
sudo systemctl reload apache2
À partir de là, Apache peut utiliser la vraie IP du visiteur, transmise par Varnish via X-Forwarded-For.
Configurer Varnish pour transmettre X-Forwarded-For
Il faut maintenant vérifier que Varnish transmet bien l’adresse IP du client au backend Apache.
Dans votre fichier VCL, souvent :
/etc/varnish/default.vclCode language: JavaScript (javascript)
ajoutez ou vérifiez ceci dans vcl_recv :
sub vcl_recv {
if (req.http.X-Forwarded-For) {
set req.http.X-Forwarded-For = req.http.X-Forwarded-For + ", " + client.ip;
} else {
set req.http.X-Forwarded-For = client.ip;
}
}Code language: JavaScript (javascript)
Cette configuration conserve une éventuelle chaîne existante, puis ajoute l’IP du client vu par Varnish.
Dans certaines architectures simples, on peut aussi choisir de réinitialiser le header pour éviter de faire confiance à une valeur envoyée par le client :
sub vcl_recv {
unset req.http.X-Forwarded-For;
set req.http.X-Forwarded-For = client.ip;
}Code language: JavaScript (javascript)
Cette seconde approche est souvent préférable si Varnish est le premier proxy de confiance et qu’aucun proxy fiable ne se trouve devant lui. Elle évite qu’un visiteur forge lui-même un header X-Forwarded-For.
Après modification, vérifiez puis rechargez Varnish :
sudo varnishd -C -f /etc/varnish/default.vcl
sudo systemctl reload varnishCode language: JavaScript (javascript)
Adapter les logs Apache
Avec mod_remoteip, Apache remplace l’adresse IP client utilisée par la requête. Cependant, il faut aussi vérifier le format des logs.
Le format classique utilise souvent %h :
LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combinedCode language: JavaScript (javascript)
Avec mod_remoteip, %a est souvent préférable pour enregistrer l’adresse IP cliente effective :
LogFormat "%a %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combinedCode language: JavaScript (javascript)
La documentation Apache précise que le module peut modifier l’adresse IP cliente visible par Apache, et mentionne aussi le format de log permettant de suivre les adresses intermédiaires.
Sur Debian/Ubuntu, les formats de logs se trouvent souvent dans :
/etc/apache2/apache2.conf
Après modification :
sudo apachectl configtest
sudo systemctl reload apache2
Tester que la vraie IP est bien restaurée
Pour tester rapidement, créez un petit fichier PHP temporaire sur le site :
<?php
header( 'Content-Type: text/plain; charset=utf-8' );
echo 'REMOTE_ADDR=' . ( $_SERVER['REMOTE_ADDR'] ?? '' ) . PHP_EOL;
echo 'HTTP_X_FORWARDED_FOR=' . ( $_SERVER['HTTP_X_FORWARDED_FOR'] ?? '' ) . PHP_EOL;Code language: HTML, XML (xml)
Appelez-le ensuite depuis votre navigateur ou avec curl. Si tout fonctionne, REMOTE_ADDR doit afficher l’adresse IP réelle du visiteur, et non 127.0.0.1.
Supprimez ce fichier après le test. Une page qui affiche les headers et IPs n’a rien à faire en production, sauf si vous aimez offrir des petites cartes postales de votre stack.
Cas WordPress : commentaires et anti-spam
Dans WordPress, l’adresse IP du commentateur est enregistrée dans la colonne comment_author_IP de la table des commentaires. Si Apache transmet 127.0.0.1 à PHP, WordPress enregistrera donc 127.0.0.1 pour tous les commentaires.
Une fois mod_remoteip correctement configuré, $_SERVER['REMOTE_ADDR'] contient de nouveau la bonne IP, et WordPress peut enregistrer la véritable adresse du visiteur.
C’est important pour :
- les logs Apache ;
- les commentaires WordPress ;
- les plugins anti-spam ;
- les règles de sécurité ;
- les limitations par IP ;
- les outils d’analyse serveur ;
- les diagnostics de trafic.
Sans cela, tous les visiteurs semblent venir du même endroit. Pour un plugin anti-spam, c’est un peu comme essayer d’identifier des gens dans une pièce où tout le monde porte le même badge “127.0.0.1”.
Sécurité : ne jamais faire confiance à X-Forwarded-For sans proxy fiable
Point très important : n’utilisez jamais X-Forwarded-For comme source de vérité si la requête ne vient pas d’un proxy de confiance.
Un visiteur peut envoyer lui-même un header X-Forwarded-For :
curl -H "X-Forwarded-For: 1.2.3.4" https://example.com/Code language: JavaScript (javascript)
Si votre serveur fait confiance à ce header sans vérifier que la requête vient bien de Varnish, Cloudflare, HAProxy ou Nginx, n’importe qui peut falsifier son IP.
C’est pour cela que RemoteIPTrustedProxy est essentiel :
RemoteIPHeader X-Forwarded-For
RemoteIPTrustedProxy 127.0.0.1
RemoteIPTrustedProxy ::1Code language: CSS (css)
Apache ne doit faire confiance au header que s’il provient d’un proxy connu et maîtrisé.
Cas Cloudflare : utiliser CF-Connecting-IP
Si votre reverse proxy est Cloudflare, le header recommandé n’est pas forcément X-Forwarded-For. Cloudflare transmet l’IP originale du visiteur dans CF-Connecting-IP. Sa documentation indique que, par défaut, l’origine voit une IP Cloudflare, et que l’adresse IP du visiteur original est transmise dans ce header.
La configuration Apache ressemble alors à ceci :
RemoteIPHeader CF-Connecting-IP
# Exemples uniquement : utilisez la liste officielle Cloudflare à jour.
RemoteIPTrustedProxy 173.245.48.0/20
RemoteIPTrustedProxy 103.21.244.0/22
RemoteIPTrustedProxy 103.22.200.0/22
RemoteIPTrustedProxy 103.31.4.0/22
RemoteIPTrustedProxy 141.101.64.0/18
RemoteIPTrustedProxy 108.162.192.0/18
RemoteIPTrustedProxy 190.93.240.0/20
RemoteIPTrustedProxy 188.114.96.0/20
RemoteIPTrustedProxy 197.234.240.0/22
RemoteIPTrustedProxy 198.41.128.0/17Code language: PHP (php)
Il faut maintenir la liste des plages Cloudflare à jour, car ce sont elles qui déterminent à quels proxies Apache fait confiance.
Si votre chaîne est Cloudflare → Varnish → Apache, vous devez être cohérent : Cloudflare transmet CF-Connecting-IP à Varnish, Varnish peut ensuite transmettre une IP propre à Apache via X-Forwarded-For, ou conserver une stratégie explicite. L’essentiel est de ne pas faire confiance à un header qui peut être forgé par le client final.
Cas Nginx devant Apache
Si Nginx est placé devant Apache, la logique est la même. Nginx doit transmettre l’IP originale :
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;Code language: PHP (php)
Puis Apache doit faire confiance à l’IP de Nginx :
RemoteIPHeader X-Forwarded-For
RemoteIPTrustedProxy 127.0.0.1
RemoteIPTrustedProxy ::1Code language: CSS (css)
Si Nginx est sur une machine séparée, remplacez 127.0.0.1 par son IP privée ou son CIDR interne :
RemoteIPTrustedProxy 10.0.0.10Code language: CSS (css)
Cas HAProxy : PROXY protocol ou X-Forwarded-For
Avec HAProxy, deux approches existent souvent :
- transmettre
X-Forwarded-For; - utiliser le PROXY protocol.
Pour une configuration simple en headers HTTP, HAProxy peut ajouter :
option forwardfor
Côté Apache :
RemoteIPHeader X-Forwarded-For
RemoteIPTrustedProxy 10.0.0.20Code language: CSS (css)
Pour le PROXY protocol, la configuration est différente et doit être prise en charge explicitement par le serveur cible. Ne mélangez pas les deux sans comprendre votre chaîne de proxies.
Cas de plusieurs proxies en chaîne
Dans une architecture moderne, il peut y avoir plusieurs couches :
Visiteur → Cloudflare → Varnish → Apache → PHP-FPM
Dans ce cas, le header X-Forwarded-For peut contenir plusieurs IPs :
X-Forwarded-For: 203.0.113.42, 198.51.100.10, 127.0.0.1Code language: CSS (css)
Apache doit pouvoir distinguer les proxies de confiance des IPs clientes. C’est précisément le rôle de RemoteIPTrustedProxy et des variantes disponibles dans mod_remoteip.
Ne déclarez pas tout Internet comme proxy fiable. Déclarez uniquement vos IPs internes, votre CDN, ou les plages officielles du proxy que vous utilisez. Sinon, le header devient une machine à fabriquer de fausses IPs.
Diagnostic rapide
Voici les commandes utiles pour diagnostiquer le problème.
Vérifier les modules Apache :
apachectl -M | grep remoteip
Vérifier la configuration chargée :
grep -R "RemoteIP" /etc/apache2Code language: JavaScript (javascript)
Tester Apache :
sudo apachectl configtest
Voir les logs :
sudo tail -f /var/log/apache2/access.log
sudo tail -f /var/log/apache2/error.logCode language: JavaScript (javascript)
Tester avec un header forgé depuis l’extérieur :
curl -H "X-Forwarded-For: 1.2.3.4" https://example.com/ip-test.phpCode language: JavaScript (javascript)
Si votre serveur accepte cette fausse IP alors que la requête ne vient pas d’un proxy fiable, votre configuration est dangereuse. Elle fait confiance au mauvais interlocuteur.
Configuration complète : Varnish local devant Apache
Voici une configuration moderne pour le cas simple :
Visiteur → Varnish local → Apache
Dans Varnish :
sub vcl_recv {
unset req.http.X-Forwarded-For;
set req.http.X-Forwarded-For = client.ip;
}Code language: JavaScript (javascript)
Dans Apache :
RemoteIPHeader X-Forwarded-For
RemoteIPTrustedProxy 127.0.0.1
RemoteIPTrustedProxy ::1Code language: CSS (css)
Activation :
sudo a2enmod remoteip
sudo a2enconf remoteip
sudo apachectl configtest
sudo systemctl reload apache2
sudo systemctl reload varnish
Avec cette configuration, Apache et PHP récupèrent l’IP réelle du visiteur, mais uniquement parce que la requête provient de Varnish, déclaré comme proxy fiable.
Mémo rapide
# Ancienne méthode obsolète.
apt-get install libapache2-mod-rpaf
a2enmod rpaf
# Méthode moderne Apache 2.4.
sudo a2enmod remoteip
# Configuration Apache.
sudo nano /etc/apache2/conf-available/remoteip.conf
# Varnish local.
RemoteIPHeader X-Forwarded-For
RemoteIPTrustedProxy 127.0.0.1
RemoteIPTrustedProxy ::1
# Activer la conf.
sudo a2enconf remoteip
# Tester Apache.
sudo apachectl configtest
# Recharger Apache.
sudo systemctl reload apache2
# Varnish : transmettre la vraie IP.
sub vcl_recv {
unset req.http.X-Forwarded-For;
set req.http.X-Forwarded-For = client.ip;
}
# Vérifier le module.
apachectl -M | grep remoteip
# Suivre les logs.
sudo tail -f /var/log/apache2/access.logCode language: PHP (php)
Conclusion
Lorsqu’Apache est placé derrière un reverse proxy comme Varnish, il voit par défaut l’adresse IP du proxy, et non celle du visiteur. C’est pourquoi WordPress peut enregistrer 127.0.0.1 comme IP de tous les commentateurs.
L’ancienne solution consistait à installer mod_rpaf. Aujourd’hui, la solution propre avec Apache 2.4 consiste à utiliser mod_remoteip :
RemoteIPHeader X-Forwarded-For
RemoteIPTrustedProxy 127.0.0.1
RemoteIPTrustedProxy ::1Code language: CSS (css)
Côté Varnish, il faut transmettre l’IP du client dans X-Forwarded-For. Côté Apache, il faut faire confiance uniquement aux IPs des proxies maîtrisés.
Une fois configuré, les logs Apache, WordPress, les commentaires et les outils anti-spam retrouvent la véritable adresse IP des visiteurs. Le serveur arrête enfin de croire que toute l’humanité vit sur 127.0.0.1, ce qui est philosophiquement charmant, mais techniquement inutile.
Vous imaginez un projet WordPress ou WooCommerce ? Je vous accompagne à chaque étape pour concrétiser vos ambitions, avec rigueur et transparence.
Mise à jour de l’article : j’avais oublié la ligne qui active le module (merci Elias).