Confusion Apache vs Nginx sur Ubuntu 24.04 : résoudre proprement les liaisons de ports et les boucles de proxy (cas n°94)

Cet article vous a aidé ?

Douleur : votre serveur « fonctionnait hier », vous avez installé « juste une chose », et maintenant le port 80 est « déjà utilisé », les redirections TLS tournent en boucle, ou Nginx fait du proxy vers… lui-même. Rien n’est en feu, mais votre graphe de disponibilité transpire.

Ubuntu 24.04 rend facile l’installation à la fois d’Apache et de Nginx, parfois sans que vous vous en rendiez compte. L’astuce n’est pas de « redémarrer tout jusqu’à ce que ça marche ». L’astuce est de décider qui possède quel port, puis de faire en sorte que vos proxies et en-têtes disent la vérité.

Playbook de diagnostic rapide

Quand Apache et Nginx sont tous deux présents, la voie la plus rapide est de répondre à trois questions dans l’ordre. Ne touchez pas aux configs tant que vous ne pouvez pas y répondre avec des preuves.

1) Qui écoute réellement sur 80 et 443 ?

Si vous ne savez pas quel processus possède les sockets, vous devinez. Deviner, c’est créer des « correctifs temporaires » qui survivent des années.

  • Vérifiez les écouteurs avec ss et mappez les PID aux services.
  • Si vous voyez apache2 et nginx essayer tous les deux de posséder :80, vous avez déjà trouvé la première faille.

2) Où le trafic va-t-il après le premier accept() ?

Confirmez si Nginx reverse-proxifie vers Apache (commun), Apache proxifie vers autre chose, ou si les deux proxifient en boucle.

  • Utilisez curl -v pour voir les en-têtes Location:, les codes de statut, et si la détection HTTPS est erronée.
  • Inspectez les hôtes virtuels actifs sur les deux serveurs (apachectl -S, nginx -T).

3) Les redirections et décisions de « schéma » sont-elles basées sur la réalité ?

La plupart des boucles de proxy ne sont pas des « problèmes réseau ». Ce sont des problèmes de vérité : le backend pense que la requête est HTTP alors que le client est passé par HTTPS, donc il continue de rediriger « vers HTTPS » indéfiniment.

  • Corrigez les en-têtes : X-Forwarded-Proto, X-Forwarded-Host, et (si nécessaire) la gestion RemoteIP d’Apache.
  • Corrigez les attentes de port et de schéma du backend (vhost Apache sur 127.0.0.1:8080, pas 80).

Porte de décision : Si vous ne pouvez pas décrire le chemin de la requête en une phrase (« Le client frappe Nginx :443, proxy vers Apache :8080 sur loopback, Apache sert l’app avec les en-têtes de schéma corrects »), arrêtez-vous et cartographiez-le. Aucun changement tant que vous ne pouvez pas le narrer.

Faits intéressants et contexte (pourquoi cela se reproduit)

  1. Nginx a été conçu pour le problème C10k. Le design événementiel d’Igor Sysoev (début des années 2000) est devenu un choix par défaut pour les frontends à haute simultanéité.
  2. Les modèles de processus / threads d’Apache ont évolué pour d’autres époques. Les MPM Prefork, worker et event existent parce que « une taille unique » ne convient jamais en production.
  3. Ubuntu a historiquement fait d’Apache le serveur web par défaut. Cette habitude persiste ; beaucoup de paquets supposent qu’Apache est présent ou peut être activé.
  4. Systemd a introduit l’activation de socket en tant que citoyen de première classe. Il peut garder un port ouvert et démarrer un service à la demande, ce qui embrouille le débogage « qui écoute ? » si vous ne vérifiez pas les types d’unités.
  5. Les en-têtes de reverse proxy sont une norme de fait, pas une spécification unique. X-Forwarded-* est largement utilisé mais interprété de manière inégale par les apps et frameworks.
  6. « Trop de redirections » est généralement déterministe. Ça semble aléatoire car les caches et HSTS peuvent l’amplifier, mais la boucle est reproductible avec curl -v.
  7. Le port 80 est spécial parce que tout le monde le veut. Captive portals, défis ACME HTTP-01, sites par défaut et « serveurs de debug temporaires » se disputent tous ce port.
  8. La culture a2enmod / a2ensite d’Apache est différente des liens symboliques sites-enabled de Nginx. Mélanger les modèles mentaux mène à des moments « j’ai changé un fichier, pourquoi ça n’a pas pris effet ? ».

Modèle mental : bind, accept, proxy, redirect

La confusion dans la pile web concerne rarement « quel serveur est meilleur ». Il s’agit de la propriété des sockets et de la clarté d’intention.

Binding : un seul processus possède un port TCP

Sur un hôte Linux normal, 0.0.0.0:80 ne peut être lié que par un seul processus à la fois. Si vous lancez Apache après que Nginx a déjà lié :80, Apache échouera (ou l’inverse). Si systemd tient le port pour activation de socket, les deux peuvent « sembler démarrer » tandis que l’un ne peut pas réellement recevoir le trafic.

Acceptation : le premier serveur donne le ton

Le serveur qui accepte la connexion contrôle la terminaison TLS, HTTP/2 vs HTTP/1.1, les logs d’accès en périphérie et les décisions d’« IP client réelle ». Tout ce qui est derrière lui est un backend. Traitez-le comme une couche edge, même si c’est sur la même VM.

Proxying : une direction, pas de boomerang

Un reverse proxy doit envoyer le trafic vers un port ou une destination différente. La manière la plus simple de créer une boucle accidentelle est de proxyfier vers un nom d’hôte qui résout vers le même écouteur (ou vers le même port via des règles NAT). « Ça marche sur mon laptop » devient « fond en production » parce que DNS et /etc/hosts diffèrent.

Redirection : l’application doit savoir ce que le client a vu

Les backends génèrent souvent des redirections absolues basées sur le schéma et l’hôte perçus. Si TLS se termine sur Nginx mais qu’Apache voit du HTTP simple sur 127.0.0.1:8080, Apache (ou votre app) peut insister sur « vous devez être en HTTPS » et rediriger vers HTTPS. Le navigateur revient vers Nginx HTTPS, qui renvoie du HTTP, et le backend redirige à nouveau. Voilà la boucle.

Il y a deux vérités propres que vous pouvez enseigner à votre backend :

  • Dites-lui le schéma original via X-Forwarded-Proto: https.
  • Ou faites en sorte que le backend parle réellement HTTPS aussi (moins courant en local ; généralement inutile).

Une citation, une règle de vie : la paraphrase d’Andrew S. Grove « Only the paranoid survive » est utile pour l’exploitation : supposez que vos valeurs par défaut vous trahiront sauf si elles sont vérifiées.

Tâches pratiques (commandes, sorties attendues, décisions)

Voici des tâches réelles que vous pouvez exécuter sur Ubuntu 24.04 pour diagnostiquer et corriger les liaisons de ports et les boucles de proxy. Chaque tâche inclut : commande, signification de la sortie, et la décision à prendre.

Task 1: Identify who listens on 80/443 (the only starting point that matters)

cr0x@server:~$ sudo ss -ltnp '( sport = :80 or sport = :443 )'
State  Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0      511          0.0.0.0:80        0.0.0.0:*     users:(("nginx",pid=1274,fd=6))
LISTEN 0      511          0.0.0.0:443       0.0.0.0:*     users:(("nginx",pid=1274,fd=7))

Ce que cela signifie : Nginx possède les deux sockets. Apache n’est pas la couche edge, même s’il est installé.

Décision : Si vous voulez Apache derrière Nginx, déplacez Apache hors de 80/443 (par ex. vers 8080). Si vous voulez Apache comme seul serveur, arrêtez/désactivez Nginx.

Task 2: Check if Apache is failing due to bind errors

cr0x@server:~$ sudo journalctl -u apache2 -n 50 --no-pager
Aug 14 09:12:10 server apachectl[2219]: (98)Address already in use: AH00072: make_sock: could not bind to address 0.0.0.0:80
Aug 14 09:12:10 server apachectl[2219]: AH00451: no listening sockets available, shutting down
Aug 14 09:12:10 server apachectl[2219]: AH00015: Unable to open logs

Ce que cela signifie : Apache est configuré pour écouter sur le port 80 et ne peut pas le faire.

Décision : Soit arrêtez l’autre écouteur (souvent Nginx), soit changez la directive Listen d’Apache vers un port backend.

Task 3: Confirm systemd unit states (enabled vs running vs socket-activated)

cr0x@server:~$ systemctl status nginx --no-pager
● nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: enabled)
     Active: active (running) since Wed 2025-08-14 09:10:22 UTC; 4min 18s
       Docs: man:nginx(8)
   Main PID: 1274 (nginx)
      Tasks: 3 (limit: 18931)
     Memory: 6.8M
        CPU: 78ms
cr0x@server:~$ systemctl status apache2 --no-pager
● apache2.service - The Apache HTTP Server
     Loaded: loaded (/usr/lib/systemd/system/apache2.service; enabled; preset: enabled)
     Active: failed (Result: exit-code) since Wed 2025-08-14 09:12:10 UTC; 2min 30s ago
   Main PID: 2219 (code=exited, status=1/FAILURE)

Ce que cela signifie : Nginx est sain ; Apache a échoué. Ce n’est pas une situation « redémarrer les deux » ; c’est une décision d’architecture.

Décision : Choisissez une topologie : (A) Nginx-only, (B) Apache-only, (C) Nginx edge + Apache backend. Puis configurez en conséquence.

Task 4: Inventory Apache listening directives and enabled sites

cr0x@server:~$ grep -R --line-number -E '^\s*Listen\s+' /etc/apache2
/etc/apache2/ports.conf:5:Listen 80
/etc/apache2/ports.conf:9:Listen 443
cr0x@server:~$ sudo apachectl -S
VirtualHost configuration:
*:80                   is a NameVirtualHost
         default server 000-default (/etc/apache2/sites-enabled/000-default.conf:1)
         port 80 namevhost example.internal (/etc/apache2/sites-enabled/example.conf:1)
*:443                  example.internal (/etc/apache2/sites-enabled/example-le-ssl.conf:2)
ServerRoot: "/etc/apache2"
Main DocumentRoot: "/var/www/html"
Main ErrorLog: "/var/log/apache2/error.log"

Ce que cela signifie : Apache est configuré pour se lier sur 80 et 443. Si Nginx est le edge, cela doit changer.

Décision : Éditez /etc/apache2/ports.conf et les vhosts associés pour lier sur 127.0.0.1:8080 (et éventuellement 8443 si nécessaire). Ou désactivez les vhosts Apache si ce n’est pas utilisé.

Task 5: Dump Nginx effective config to see real includes and upstreams

cr0x@server:~$ sudo nginx -T
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
# configuration file /etc/nginx/nginx.conf:
http {
    include /etc/nginx/mime.types;
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

Ce que cela signifie : Vous ne pouvez pas faire confiance à « j’ai édité un fichier » tant que vous ne savez pas ce qui est inclus. nginx -T vous dit la vérité.

Décision : Localisez où proxy_pass est défini et vérifiez qu’il pointe vers un port backend différent de l’écouteur.

Task 6: Check for the classic self-proxy mistake (proxying to the same host/port)

cr0x@server:~$ sudo grep -R --line-number -E 'proxy_pass\s+http' /etc/nginx/sites-enabled /etc/nginx/conf.d
/etc/nginx/sites-enabled/example.conf:18:    proxy_pass http://127.0.0.1:80;

Ce que cela signifie : Si Nginx écoute sur 80 et proxifie vers 127.0.0.1:80, il se proxifie vers lui-même. C’est une boucle qui peut ressembler à un blocage, un 502, ou un pic CPU.

Décision : Changez le backend vers un port différent (par ex. Apache sur 127.0.0.1:8080) ou une destination différente entièrement.

Task 7: Validate backend reachability (separate connectivity from HTTP behavior)

cr0x@server:~$ curl -sS -D- http://127.0.0.1:8080/ -o /dev/null
HTTP/1.1 200 OK
Date: Wed, 14 Aug 2025 09:17:02 GMT
Server: Apache/2.4.58 (Ubuntu)
Content-Type: text/html; charset=UTF-8

Ce que cela signifie : Apache est joignable sur 8080 et sert du contenu. Bien. Maintenant votre proxy peut pointer vers là.

Décision : Si cela échoue, corrigez d’abord Apache (ports, vhost, firewall, état du service). Ne « tunez pas Nginx » quand le backend est mort.

Task 8: Catch a redirect loop with headers (you can’t fix what you won’t observe)

cr0x@server:~$ curl -k -I https://example.internal/
HTTP/2 301
date: Wed, 14 Aug 2025 09:18:41 GMT
location: http://example.internal/
server: nginx
cr0x@server:~$ curl -I http://example.internal/
HTTP/1.1 301 Moved Permanently
Date: Wed, 14 Aug 2025 09:18:45 GMT
Location: https://example.internal/
Server: nginx

Ce que cela signifie : HTTPS redirige vers HTTP, et HTTP redirige vers HTTPS. C’est une boucle à deux nœuds. Habituellement des règles de redirection mal configurées ou des configs conflictuelles dans différents vhosts.

Décision : Choisissez un schéma canonique (généralement HTTPS) et appliquez-le en exactement un endroit (généralement le serveur edge). Supprimez les redirections concurrentes dans le backend.

Task 9: Inspect headers Nginx forwards (scheme and host are the usual culprits)

cr0x@server:~$ sudo grep -n 'proxy_set_header' -n /etc/nginx/sites-enabled/example.conf
22:    proxy_set_header Host $host;
23:    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
24:    proxy_set_header X-Forwarded-Proto $scheme;

Ce que cela signifie : Si X-Forwarded-Proto n’est pas défini, beaucoup d’apps vont mal détecter HTTPS et générer de mauvaises redirections ou du contenu mixte.

Décision : Assurez-vous que Host et X-Forwarded-Proto sont définis. Si vous terminez TLS sur Nginx, $scheme doit être https pour les requêtes client HTTPS.

Task 10: Verify Apache sees the forwarded scheme and doesn’t “upgrade” again

cr0x@server:~$ sudo grep -R --line-number -E 'RewriteRule|RewriteCond|HTTPS|X-Forwarded-Proto' /etc/apache2/sites-enabled
/etc/apache2/sites-enabled/example.conf:12:RewriteCond %{HTTPS} !=on
/etc/apache2/sites-enabled/example.conf:13:RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

Ce que cela signifie : Apache effectue une redirection HTTPS basée sur sa propre variable %{HTTPS}. Derrière un proxy SSL-terminant, Apache pensera que HTTPS est désactivé.

Décision : Supprimez cette redirection d’Apache quand Nginx est le edge, ou réécrivez la condition pour respecter X-Forwarded-Proto afin que le backend ne lutte pas contre le frontend.

Task 11: Check for duplicate/default sites shadowing your intended host

cr0x@server:~$ ls -l /etc/nginx/sites-enabled
total 0
lrwxrwxrwx 1 root root 34 Aug 14 09:05 default -> /etc/nginx/sites-available/default
lrwxrwxrwx 1 root root 34 Aug 14 09:06 example.conf -> /etc/nginx/sites-available/example.conf

Ce que cela signifie : Le site Nginx par défaut peut capturer le trafic que vous attendiez ailleurs, selon la correspondance server_name et les règles default_server.

Décision : Désactivez le site par défaut s’il n’est pas nécessaire, ou assurez-vous que votre site prévu est marqué default_server pour les lignes listen pertinentes.

Task 12: Confirm the “real” config is loaded and reload safely

cr0x@server:~$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
cr0x@server:~$ sudo systemctl reload nginx

Ce que cela signifie : La syntaxe est valide, et le reload applique les changements sans fermer les connexions existantes (la plupart du temps, c’est ce que vous voulez).

Décision : Préférez reload plutôt que restart pour Nginx/Apache lors de l’application de changements en production, sauf si vous changez des modules bas-niveau ou si le service est bloqué.

Task 13: Check DNS/hosts resolution to avoid “proxy to myself” by name

cr0x@server:~$ getent hosts example.internal
10.10.20.15    example.internal

Ce que cela signifie : Si Nginx proxifie vers http://example.internal et que cela résout vers l’IP publique du serveur, vous pouvez revenir vers Nginx au lieu d’atteindre Apache.

Décision : Proxyfiez vers 127.0.0.1 (ou une IP backend dédiée) et un port backend. Évitez d’utiliser le nom public pour des backends locaux à moins de vouloir explicitement traverser la route edge.

Task 14: Verify UFW isn’t “helping” by blocking your backend port

cr0x@server:~$ sudo ufw status verbose
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), deny (routed)
New profiles: skip

To                         Action      From
OpenSSH                    ALLOW IN    Anywhere
80/tcp                     ALLOW IN    Anywhere
443/tcp                    ALLOW IN    Anywhere

Ce que cela signifie : Le port backend 8080 n’est pas autorisé depuis Internet, ce qui est bien. Mais si Apache se lie sur 127.0.0.1:8080, UFW n’a pas d’effet pour le loopback de toute façon.

Décision : Lie les services backend au loopback. N’ouvrez pas les ports backend sauf si vous avez vraiment besoin d’un accès distant.

Task 15: Confirm Apache is actually listening where you think after changes

cr0x@server:~$ sudo ss -ltnp | grep -E ':(80|8080|443|8443)\b'
LISTEN 0      511        127.0.0.1:8080    0.0.0.0:* users:(("apache2",pid=3012,fd=4))
LISTEN 0      511          0.0.0.0:80      0.0.0.0:* users:(("nginx",pid=1274,fd=6))
LISTEN 0      511          0.0.0.0:443     0.0.0.0:* users:(("nginx",pid=1274,fd=7))

Ce que cela signifie : C’est la séparation propre que vous voulez : Nginx possède les ports Internet, Apache reste privé sur le loopback.

Décision : Verrouillez cela : assurez-vous qu’Apache n’est pas configuré pour se lier aux interfaces publiques, et assurez-vous qu’aucun autre service ne prendra 8080 de façon inattendue.

Task 16: Look for 502/504 root causes in logs (don’t stare at the browser)

cr0x@server:~$ sudo tail -n 30 /var/log/nginx/error.log
2025/08/14 09:22:33 [error] 1274#1274: *118 connect() failed (111: Connection refused) while connecting to upstream, client: 10.10.20.33, server: example.internal, request: "GET / HTTP/2.0", upstream: "http://127.0.0.1:8080/", host: "example.internal"

Ce que cela signifie : Nginx ne peut pas se connecter à l’upstream ; Apache est down, écoute ailleurs, ou est bloqué.

Décision : Corrigez l’état et la liaison du service backend, pas les timeouts Nginx. Connection refused n’est pas un problème de performance.

Blague courte #1 : Si vous « avez réparé » un conflit de ports en redémarrant, félicitations — vous avez appris au problème à mieux se cacher.

Modèles de correction propres (choisissez-en un et engagez-vous)

Vous pouvez faire fonctionner Apache et Nginx ensemble. Vous pouvez aussi n’en exécuter qu’un seul. Le mode défaillant vient de l’exécution des deux avec des responsabilités qui se chevauchent et aucune propriété claire des ports.

Pattern A: Nginx edge (80/443) + Apache backend (127.0.0.1:8080)

C’est l’état final « les deux installés » le plus courant et qui reste sain. Nginx gère TLS, HTTP/2, les fichiers statiques si vous le souhaitez, le buffering et le routage simple. Apache sert les apps PHP (via PHP-FPM ou mod_php) ou les configurations héritées basées sur .htaccess. Apache ne touche jamais aux ports 80/443 sur l’interface publique.

Apache : quittez 80/443

Éditez /etc/apache2/ports.conf pour ressembler à :

cr0x@server:~$ sudo sed -n '1,120p' /etc/apache2/ports.conf
# If you just change these ports or add more ports here, you will likely also
# have to change the VirtualHost statement in
# /etc/apache2/sites-enabled/000-default.conf

Listen 127.0.0.1:8080

Puis assurez-vous que votre vhost site écoute sur 8080 :

cr0x@server:~$ sudo grep -n '<VirtualHost' /etc/apache2/sites-enabled/example.conf
1:<VirtualHost 127.0.0.1:8080>

Décision : Lier explicitement à 127.0.0.1 empêche l’exposition accidentelle et élimine les discussions « pourquoi UFW bloque mon backend ? ».

Nginx : proxy vers Apache et définissez les bons en-têtes

Un bloc serveur Nginx minimal et honnête ressemble à ceci (conceptuellement) :

  • proxy_pass http://127.0.0.1:8080;
  • Transmettre Host et le schéma via X-Forwarded-Proto
  • Optionnellement définir X-Forwarded-Host et X-Forwarded-Port

Décision : L’objectif est que Apache/l’application puisse reconstruire exactement l’URL externe.

Pattern B: Apache only (80/443) and remove Nginx from the path

Si vous n’utilisez pas les fonctionnalités de Nginx, ne l’exécutez pas « juste parce que ». Les piles plus simples sont plus faciles à déboguer à 3h du matin.

Faites ceci :

  • Arrêtez et désactivez Nginx.
  • Assurez-vous qu’Apache se lie sur 80/443 et a les bons vhosts.

Et ne faites pas ceci :

  • Laissez Nginx activé « au cas où on en aurait besoin plus tard ». C’est comme ça qu’on obtient des conflits de ports surprises lors d’upgrades de paquets.

Pattern C: Nginx only and remove Apache from the path

Si le « backend » est en fait un serveur d’app moderne (Gunicorn, uWSGI, Node, etc.), vous n’avez peut-être pas besoin d’Apache du tout. Nginx peut reverse-proxifier directement vers l’app, et vous évitez le saut supplémentaire.

Décision : Si votre seule raison pour Apache est « il venait avec le tutoriel », supprimez ce tutoriel de votre cerveau et passez à autre chose.

Boucles de proxy et enfer des redirections : comment repérer et corriger

Il y a deux grandes classes de boucles :

  • Boucles réseau/proxy : proxy vers soi-même (même hôte/port), ou un nom qui résout vers l’écouteur edge.
  • Boucles de redirection : le backend continue de rediriger parce qu’il pense que schéma/hôte/port diffèrent de ce que le client a utilisé.

Loop type 1: Nginx proxies to itself

Symptôme typique : Les requêtes se bloquent, le CPU monte, le log d’erreur Nginx montre des timeouts d’upstream, ou vous voyez beaucoup de connexions internes vers lui-même.

Cause racine : proxy_pass http://127.0.0.1:80 alors que Nginx écoute sur 80 ; ou proxy_pass http://example.internal et DNS résout vers la même IP que Nginx écoute.

Correctif : Assurez-vous que l’upstream est une destination différente : loopback sur un port différent, un socket Unix, ou un hôte différent. Utilisez IP:port explicite pour éviter les surprises DNS.

Loop type 2: HTTP ↔ HTTPS redirect ping-pong

Symptôme typique : Le navigateur affiche « trop de redirections », curl -I montre des Location: http:// et Location: https:// alternants.

Cause racine : Une couche force HTTPS, une autre force HTTP, ou le backend force HTTPS parce qu’il ne comprend pas qu’il est derrière une terminaison TLS.

Correctif : Choisissez un point d’application. Habituellement : Nginx force HTTP→HTTPS, le backend ne le fait pas. Enseignez le schéma au backend via X-Forwarded-Proto et assurez-vous que l’app le fait confiance correctement.

Loop type 3: Canonical host redirects fighting server_name

Symptôme typique : Vous demandez www, ça redirige vers l’apex, puis retour à www.

Cause racine : Deux configs différentes pensent chacune gérer la canonicalisation : rewrite Nginx + paramètre canonique côté app.

Correctif : Décidez où la canonicalisation vit. Pour la plupart des organisations : faites-la à la périphérie (Nginx) et désactivez les redirections canoniques côté app sauf si nécessaire pour le multi-tenant.

Loop type 4: ACME/Let’s Encrypt challenge routes incorrectly

Symptôme typique : L’émission du certificat échoue, et le endpoint de challenge retourne une redirection ou un 404 depuis le mauvais serveur.

Cause racine : Vous avez les deux serveurs essayant de servir /.well-known/acme-challenge/, ou le proxy envoie ce chemin à un backend qui redirige vers HTTPS alors que la CA s’attend à du HTTP simple.

Correctif : Servez le challenge ACME directement sur le vhost HTTP edge et bypasser les redirections backend pour ce chemin.

Blague courte #2 : Une boucle de redirection est la façon du web de vous dire : « J’ai entendu que vous aimez les allers-retours. »

Trois mini-récits d’entreprise issus du terrain

Mini-récit 1 : L’incident causé par une mauvaise hypothèse

Ils ont migré un tableau de bord interne vers Ubuntu 24.04 sur une VM fraîche. L’ingénieur chargé du cutover a installé « un serveur web » par habitude. Apache est arrivé en premier. Plus tard dans la journée, quelqu’un a ajouté Nginx parce que l’équipe sécurité voulait un profil de suite TLS spécifique utilisé ailleurs. Personne n’a retiré Apache, parce que pourquoi le faire ? Ça ne « gênait » rien.

Pendant la fenêtre de changement, ils ont mis à jour le DNS vers la nouvelle VM. Le trafic est arrivé, et le tableau de bord se chargeait… parfois. La moitié du temps, il servait la page par défaut d’Apache. L’autre moitié, il servait l’app correcte. L’équipe a fait ce que font les équipes sous pression : redémarrage des services, vidage des caches, et blâme de la propagation DNS comme s’il s’agissait d’un événement météo.

La cause racine était douloureusement ennuyeuse. Nginx était lié à 80/443, mais Apache était aussi activé et configuré pour 80/443. Apache ne pouvait pas binder, donc il a échoué. Cependant, l’équipe app continuait de tester via un port-forward local qui atteignait le port interne d’Apache depuis une config précédente, et ils ont supposé que le trafic production suivait le même chemin. Ce n’était pas le cas.

Le « fix » a été de décider d’une topologie et de l’appliquer : Nginx en edge, Apache sur 127.0.0.1:8080. Puis ils ont fait pointer la vérification de santé vers le endpoint edge, pas le backend. L’incident s’est terminé non pas par des héroïsmes, mais par une compréhension partagée que les suppositions ne sont pas de l’architecture.

Mini-récit 2 : L’optimisation qui s’est retournée contre eux

Une autre entreprise avait une app PHP legacy derrière Apache. Quelqu’un voulait de meilleures performances et a lu que Nginx est « plus rapide ». Ils ont placé Nginx devant Apache comme reverse proxy. Raisonnable. Puis ils ont voulu optimiser : activation d’un cache agressif et ajout d’un ensemble de rewrites pour forcer HTTPS et l’hôte canonique aux deux couches « par sécurité ».

En quelques heures, les utilisateurs se sont plaints de ne plus pouvoir se connecter. Les sessions étaient perdues, et l’app redirigeait aléatoirement entre HTTP et HTTPS. Le monitoring montrait une montée des réponses 301 et un taux suspectement bas de 200. Le cache Nginx servait fièrement des redirections mises en cache. C’est une façon spéciale de se tirer une balle dans le pied : vous prenez une erreur de configuration et la rendez plus rapide.

Le débogage a pris plus de temps que nécessaire parce que l’équipe regardait le comportement du navigateur. Une fois qu’ils ont basculé sur curl -v et comparé les en-têtes edge vs backend, c’était évident. Le rewrite d’Apache forçait HTTPS parce qu’il ne savait pas que TLS était terminé sur Nginx. Nginx forçait aussi HTTPS, mais avec une règle canonique légèrement différente. Le client rebondissait entre les deux.

La correction a été de supprimer la moitié de la « cleverness ». L’application de HTTPS est restée uniquement sur Nginx. Apache a arrêté de rediriger et a fait confiance à X-Forwarded-Proto pour la logique de schéma quand nécessaire. Les règles de cache ont exclu les redirections et les chemins authentifiés. Les performances se sont améliorées, mais plus important encore, le comportement s’est stabilisé. La leçon : optimiser une mauvaise compréhension produit juste des mauvaises réponses plus rapidement.

Mini-récit 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise

Une équipe fintech exécutait Nginx en edge et Apache en backend pour quelques outils internes anciens. Rien d’excitant. La pratique qui a compté était leur « contrat de propriété des ports » : un petit runbook interne qui précisait, en langage clair, quel service se lie à quels ports sur chaque classe d’hôtes.

Un après-midi, une mise à jour de paquet a tiré Apache sur un hôte qui auparavant n’avait que Nginx. La mise à jour a activé Apache par défaut. Au reboot, Apache a tenté de prendre le port 80 avant que Nginx ne démarre. Nginx a échoué à binder, et l’hôte a servi la page par défaut d’Apache. Cela aurait pu être une panne désordonnée.

Mais les contrôles de monitoring étaient construits à partir du même contrat. Ils ne vérifiaient pas uniquement « est-ce que le port 80 est ouvert ». Ils vérifiaient que la réponse edge contenait un en-tête attendu et que le certificat serveur correspondait au vhost prévu. L’alerte était spécifique : « edge identity mismatch », pas « site web down ».

L’on-call a suivi le runbook : vérifier les écouteurs, désactiver Apache, recharger Nginx, confirmer avec curl. Le temps de récupération total a été court, non pas parce qu’ils étaient des génies, mais parce qu’ils avaient écrit la vérité ennuyeuse et fait en sorte que le monitoring la teste.

Erreurs courantes : symptômes → cause racine → fix

Cette section est délibérément spécifique. Si vous voyez un symptôme, vous devriez pouvoir sauter vers une cause probable et une correction concrète.

1) Apache ne démarre pas : « Address already in use: AH00072 »

  • Symptômes : systemctl status apache2 montre failed ; le journal montre une erreur de bind sur 0.0.0.0:80 ou :443.
  • Cause racine : Nginx (ou un autre service) possède déjà le port ; ou l’activation de socket systemd le tient.
  • Fix : Décidez qui possède 80/443. Si Nginx est edge, déplacez Apache sur 127.0.0.1:8080 et mettez à jour les vhosts. Si Apache est edge, arrêtez/désactivez Nginx.

2) Nginx démarre, mais vous obtenez 502 Bad Gateway

  • Symptômes : Nginx renvoie 502 ; le log d’erreur indique connect() failed (111: Connection refused) ou upstream timed out.
  • Cause racine : L’upstream n’écoute pas là où Nginx l’attend ; mauvais port ; Apache down ; firewall non pertinent si loopback mais liaison incorrecte.
  • Fix : curl l’upstream directement (127.0.0.1:8080). Corrigez la liaison et l’état du service Apache. Puis corrigez proxy_pass dans Nginx.

3) Navigateur : « Trop de redirections »

  • Symptômes : 301/302 alternants ; curl -I montre des Location rebondissants.
  • Cause racine : Application de HTTPS ou règles d’hôte canonique dupliquées à plusieurs couches ; le backend ne connaît pas le schéma original.
  • Fix : Appliquez les redirections en un seul endroit (edge). Transmettez X-Forwarded-Proto. Supprimez les règles de redirection backend ou rendez-les conditionnelles au schéma transmis.

4) Nginx se proxifie lui-même (boucle silencieuse)

  • Symptômes : Requêtes bloquées ; CPU des workers élevé ; logs Nginx montrent des timeouts upstream ; upstream égal même hôte:port.
  • Cause racine : proxy_pass pointe vers 127.0.0.1:80 alors que Nginx écoute sur 80, ou pointe vers un nom d’hôte résolvant vers le même écouteur.
  • Fix : Changez l’upstream vers un port/IP backend distinct. Préférez loopback IP:port ou socket Unix pour les upstreams locaux.

5) Mauvais site servi (page par défaut) au lieu de votre app

  • Symptômes : Vous voyez « Apache2 Ubuntu Default Page » ou la page de bienvenue Nginx de façon inattendue.
  • Cause racine : Le site par défaut est activé et est le vhost par défaut ; mismatch de server_name ; mismatch SNI sur 443.
  • Fix : Désactivez les sites par défaut que vous n’utilisez pas. Faites correspondre votre hôte prévu avec server_name et marquez default_server si nécessaire.

6) HTTP marche, HTTPS casse (ou l’inverse)

  • Symptômes : Un schéma fonctionne ; l’autre renvoie 404, boucles 301, ou backend erroné.
  • Cause racine : Différents vhost/blocs serveur pour 80 vs 443 non synchronisés ; le vhost TLS proxifie vers un upstream différent ; mismatch de certificat vhost.
  • Fix : Assurez-vous que les deux schémas routent de manière cohérente. Utilisez HTTP uniquement pour rediriger vers HTTPS au edge, puis unifiez le routage dans le bloc serveur HTTPS.

Listes de contrôle / plan étape par étape

Checklist 1: Decide your topology (stop negotiating with the universe)

  1. Avez-vous besoin des fonctions de Nginx (tuning HTTP/2, cache, routage simple, auth au edge, rate limiting) ? Si oui, placez-le en edge.
  2. Avez-vous besoin des fonctions d’Apache (comportement .htaccess legacy, dépendances mod_* existantes) ? Si oui, conservez-le en backend.
  3. Si vous n’avez besoin d’aucun, exécutez un seul serveur. Préférez moins de pièces mobiles.

Checklist 2: Nginx edge + Apache backend (clean split)

  1. Assurez-vous que Nginx écoute sur 0.0.0.0:80 et 0.0.0.0:443.
  2. Déplacez Apache vers 127.0.0.1:8080 en éditant /etc/apache2/ports.conf et les vhosts.
  3. Dans Nginx, définissez :
    • proxy_set_header Host $host;
    • proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    • proxy_set_header X-Forwarded-Proto $scheme;
  4. Supprimez les redirections HTTPS côté backend qui utilisent %{HTTPS} sauf si elles sont réécrites pour respecter le proto transmis.
  5. Testez le backend directement avec curl vers 127.0.0.1:8080, puis testez via Nginx HTTPS.
  6. Rechargez les services en toute sécurité : nginx -t puis systemctl reload nginx ; apachectl configtest puis systemctl reload apache2.

Checklist 3: Apache-only (straightforward and boring)

  1. Arrêtez et désactivez Nginx : systemctl stop nginx, systemctl disable nginx.
  2. Assurez-vous qu’Apache écoute sur 80/443 et a les bons vhosts.
  3. Confirmez avec ss -ltnp qu’Apache possède les ports.
  4. Exécutez apachectl -S et confirmez que le vhost attendu est par défaut pour chaque port.

Checklist 4: Nginx-only (modern app backend)

  1. Arrêtez et désactivez Apache si inutilisé.
  2. Proxyfiez vers le serveur d’app sur un port/socket séparé.
  3. Gardez l’application des redirections uniquement sur Nginx.
  4. Assurez-vous que les logs sont configurés pour pouvoir déboguer les échecs upstream sans deviner.

FAQ

1) Apache et Nginx peuvent-ils écouter tous les deux sur le port 80 si l’un se lie à IPv6 et l’autre à IPv4 ?

Parfois, mais vous achetez un cas limite étrange. Les règles de binding dual-stack peuvent faire sembler cela fonctionner jusqu’à ce que ça casse. Choisissez un seul propriétaire pour 80/443.

2) Dois-je utiliser 8080 ou 8000 pour le backend Apache ?

Utilisez n’importe quel port élevé non occupé, mais soyez cohérent. 8080 est conventionnel, ce qui aide les humains futurs. Liez-le à 127.0.0.1 sauf si vous avez besoin d’un accès distant.

3) Pourquoi mon app continue-t-elle de rediriger vers HTTP alors que les clients utilisent HTTPS ?

Parce que le backend voit une connexion HTTP simple depuis le proxy et ne sait pas que le client a utilisé HTTPS. Corrigez en transmettant X-Forwarded-Proto et en configurant l’app/framework pour faire confiance aux en-têtes proxy.

4) Quelle est la façon la plus rapide de prouver une boucle d’auto-proxy ?

Regardez proxy_pass et comparez-le aux sockets d’écoute. Si Nginx écoute sur :80 et proxifie vers 127.0.0.1:80, c’est la boucle. Regardez aussi les logs d’erreur Nginx pour des timeouts upstream quand l’upstream est « lui-même ».

5) Pourquoi désactiver le site Nginx par défaut est-il important ?

Parce que la sélection du vhost par défaut est déterministe mais pas toujours intuitive sous pression. Laisser un site par défaut activé augmente la probabilité qu’un nom d’hôte ou une requête IP inattendue atterrisse sur le mauvais contenu.

6) Est-il acceptable de proxyfier vers un nom d’hôte au lieu de 127.0.0.1 ?

Ça peut l’être, mais c’est plus risqué. Les changements DNS, les configurations split-horizon et les hacks /etc/hosts peuvent transformer « backend » en « porte d’entrée » à nouveau. Pour les backends locaux, préférez le loopback explicite.

7) Dois-je ouvrir le port backend dans UFW ?

Non, pas si le backend se lie à 127.0.0.1. Gardez-le privé. Si vous liez à 0.0.0.0, vous serez forcé de gérer des règles de pare-feu et vous en oublierez une tôt ou tard.

8) Pourquoi je vois des en-têtes Apache même si Nginx est devant ?

Parce que le backend définit des en-têtes Server: et que Nginx les transmet par défaut. Si cela vous importe, vous pouvez les masquer ou les remplacer, mais ne confondez pas les en-têtes avec la propriété du socket.

9) Qu’en est-il de HTTP/2, HTTP/3 et des réglages TLS ?

Cela appartient au edge. Si Nginx termine TLS, optimisez TLS et HTTP/2 là. Gardez le backend simple : HTTP/1.1 stable sur loopback est bien et facile à déboguer.

10) Comment éviter que cela se reproduise après des mises à jour de paquets ?

Désactivez les services inutilisés et surveillez l’identité, pas seulement la disponibilité. Confirmez que le bon serveur répond sur le bon port avec les bons en-têtes/certificat. « Port ouvert » est une barre trop basse.

Conclusion : prochaines étapes réalisables aujourd’hui

La « confusion » Apache vs Nginx sur Ubuntu 24.04 n’est généralement pas un mystère. Ce sont deux services qui tentent d’être la même chose, sur les mêmes ports, avec des idées divergentes sur le schéma et l’hôte. Corrigez cela en étant décisif et en vérifiant les primitives ennuyeuses : écouteurs, upstreams, en-têtes, redirections.

  1. Exécutez ss -ltnp et notez qui possède 80/443.
  2. Choisissez une topologie : Nginx-only, Apache-only, ou Nginx edge + Apache backend.
  3. Si vous proxifiez, rendez les upstreams non ambigus (127.0.0.1:8080) et transmettez X-Forwarded-Proto.
  4. Supprimez la logique de redirection dupliquée à travers les couches ; appliquez HTTPS une seule fois.
  5. Verrouillez le tout : désactivez les sites par défaut inutilisés et les services inutilisés afin que les mises à jour ne puissent pas les « ressusciter » de manière « utile ».
← Précédent
12VHPWR : comment un connecteur est devenu une légende
Suivant →
HBM expliqué : pourquoi la mémoire est devenue verticale

Laisser un commentaire