Vous installez « un serveur web » sur Ubuntu 24.04 et soudainement vous en avez deux. L’un se lie au port 80, l’autre jure l’avoir fait en premier, et votre navigateur subit une boucle de redirection qui ressemble à un tapis roulant administratif.
Le cas n°34 est classique : Apache et Nginx tous deux activés, chacun essayant de posséder 80/443, et une configuration de reverse proxy qui renvoie accidentellement la requête vers lui-même. La bonne nouvelle : on peut corriger ça sans drame. La meilleure nouvelle : vous pouvez rendre cela ennuyeux et répétable, comme aiment les systèmes de production.
À quoi ça ressemble en production (symptômes réels)
Ce problème ne se présente pas poliment. Il se manifeste par :
- Nginx ne démarre pas :
bind() to 0.0.0.0:80 failed (98: Address already in use) - Apache ne démarre pas :
(98)Address already in use: AH00072: make_sock: could not bind to address - Boucles navigateur :
ERR_TOO_MANY_REDIRECTSsans fin - Nginx 502/504 : backend injoignable, ou joignable mais protocole incorrect
- Vous faites curl localhost et obtenez le « mauvais » site : page par défaut de l’autre serveur
- Ça marche en local, échoue depuis l’extérieur : parce que la liaison diffère entre 127.0.0.1 et 0.0.0.0 ou IPv6
Les causes racines tombent presque toujours dans l’un de ces cas :
- Conflit de propriété de port : Apache et Nginx essaient d’écouter sur 80 et/ou 443.
- Récursion de proxy accidentelle : Nginx se proxifie vers lui-même, ou Apache se proxifie vers lui-même, souvent via
localhost:80. - Confusion HTTP↔HTTPS : le backend pense que c’est HTTP, le front-end pense que c’est HTTPS, les redirections rebondissent indéfiniment.
- Incompatibilité vhost basée sur le nom : l’en-tête Host n’est pas préservé ou le bloc serveur par défaut capte le trafic.
Une petite blague, parce qu’on va devenir sérieux : quand deux démons se battent pour le port 80, Linux ne choisit pas de gagnant ; il vous donne juste un fichier de log et regarde.
Mode opératoire de diagnostic rapide (premier/deuxième/troisième)
Quand vous êtes en astreinte, vous n’avez pas besoin d’un cours. Vous avez besoin d’une séquence qui révèle rapidement le goulot d’étranglement.
Voici la séquence que j’exécute quand Ubuntu 24.04 héberge une pile web confuse.
Premier : qui possède 80/443 en ce moment ?
- Vérifiez les sockets à l’écoute pour
:80et:443(IPv4 et IPv6). - Décidez quel processus doit posséder ces ports (le front-end) et forcez l’autre à se lier ailleurs ou à ne pas écouter du tout.
Second : y a‑t‑il une boucle de proxy ?
- Recherchez
proxy_pass http://localhost(Nginx) ouProxyPass http://localhost(Apache) pointant vers un port que le proxy possède lui‑même. - Suivez les redirections avec curl. Si vous voyez un motif Location qui se répète, vous êtes en boucle.
Troisième : les en‑têtes et le schéma sont‑ils correctement transmis ?
- Confirmez que
X-Forwarded-ProtoetHostatteignent le backend. - Confirmez que le backend fait confiance au proxy et ne « monte » pas vers HTTPS incorrectement.
Quatrième (seulement si nécessaire) : comportement du vhost par défaut et SNI
- Vérifiez quel vhost est par défaut et si votre server_name/ServerName correspond.
- En TLS, confirmez que SNI sélectionne le bon certificat et le bon bloc serveur.
Faits et contexte intéressants (ce qui explique pourquoi ça arrive)
Quelques faits concrets vous aident à raisonner sur le bazar au lieu de le tripoter à l’aveugle.
En voici neuf qui comptent en pratique :
- Apache est antérieur à Nginx de plusieurs années : Apache HTTP Server a démarré au milieu des années 1990 ; Nginx est arrivé au début des années 2000 pour gérer efficacement la forte concurrence.
- Le problème « C10k » a influencé la conception de Nginx : les architectures pilotées par événements sont devenues importantes quand gérer dix mille connexions simultanées n’était plus théorique.
- Les deux serveurs peuvent maintenant jouer les deux rôles : Apache peut faire du reverse proxy et terminer TLS ; Nginx peut servir du statique et proxyfier des app servers. Le choix est surtout opérationnel.
- Linux n’autorise qu’un seul écouteur par tuple IP:port : sauf si vous utilisez des astuces avancées comme
SO_REUSEPORT(et la plupart des piles web ne devraient pas). - IPv6 peut être le propriétaire caché : vous pouvez « libérer » IPv4 :80 mais Apache se lie encore sur
[::]:80, et Nginx échoue quand même. - systemd change la visibilité des échecs : vous ne « démarrez pas un démon » ; vous démarrez une unité, et les dépendances d’unité peuvent redémarrer ou laisser des choses à moitié actives.
- Les sites par défaut sont conçus pour vous attraper : Debian/Ubuntu livrent des sites activés par défaut pour Apache et Nginx. Ils sont utiles… jusqu’à ce qu’ils ne le soient plus.
- Les boucles de redirection sont souvent une confusion de schéma : le backend pense que la requête est HTTP et redirige vers HTTPS, mais le proxy a déjà fait HTTPS et a envoyé HTTP au backend.
- « localhost » est un piège dans les proxies : sur la même machine, « localhost:80 » renvoie souvent au proxy lui‑même, pas au backend prévu.
Une citation pour rester honnête : « L’espoir n’est pas une stratégie. » — Général Gordon R. Sullivan.
Choisir une architecture sensée (et s’y tenir)
La décision principale : quel démon est la porte d’entrée pour 80/443 ?
Si vous maintenez les deux, l’un est le proxy edge et l’autre est un service backend lié à une adresse loopback ou à un port alternatif. Tout le reste est un club de combat.
Schémas recommandés
- Schéma A (courant) : Nginx sur 80/443 → Apache sur 127.0.0.1:8080
Utilisez Nginx pour la terminaison TLS, HTTP/2/3 (si désiré), le buffering et le cache statique. Apache gère les applications legacy, .htaccess, ou les applis conçues autour de modules Apache. - Schéma B : Apache sur 80/443 → backends applicatifs (PHP-FPM, services upstream)
Si vous n’avez pas besoin de Nginx, ne le lancez pas. Apache sait très bien faire du reverse proxy et terminer TLS. - Schéma C : Nginx seul
Le plus propre pour de nombreux déploiements : Nginx + PHP-FPM (ou upstream app) et aucun Apache.
À éviter
- Apache et Nginx écoutant tous deux sur 80/443. Si ça « marche », c’est uniquement parce que l’un a échoué à démarrer ou n’est pas réellement lié.
- Proxifier vers localhost:80 depuis le proxy edge. C’est ainsi que l’on construit une machine à récursion infinie.
- Mélanger les responsabilités de terminaison TLS. Soit TLS se termine au proxy, soit TLS se termine au backend. N’improvisez pas en vol.
Tâches pratiques : commandes, sorties attendues et décisions (12+)
Ce sont des tâches réelles que vous pouvez exécuter sur Ubuntu 24.04 pour diagnostiquer et corriger le problème.
Chaque tâche inclut : commande, sortie d’exemple, ce que cela signifie, et la décision à prendre.
Task 1: Identify who is listening on ports 80 and 443 (IPv4/IPv6)
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:(("apache2",pid=1421,fd=4))
LISTEN 0 511 [::]:80 [::]:* users:(("apache2",pid=1421,fd=5))
LISTEN 0 511 0.0.0.0:443 0.0.0.0:* users:(("nginx",pid=1550,fd=6))
Meaning: Apache owns port 80 (both v4 and v6). Nginx owns 443. That’s already inconsistent.
Decision: Pick one front-end for both ports; usually Nginx on both 80/443, and move Apache to 127.0.0.1:8080.
Task 2: Confirm service state and recent failures
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 Mon 2025-12-30 09:12:01 UTC; 5min ago
Docs: man:nginx(8)
Main PID: 1550 (nginx)
Tasks: 3 (limit: 18906)
Memory: 8.4M
CPU: 120ms
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: active (running) since Mon 2025-12-30 09:10:44 UTC; 6min ago
Docs: https://httpd.apache.org/docs/2.4/
Main PID: 1421 (apache2)
Meaning: Both are enabled and running. That’s okay only if they are not fighting for the same ports.
Decision: If you want a proxy architecture, keep both but rebind Apache. If you don’t, stop and disable one.
Task 3: Look for “address already in use” in logs quickly
cr0x@server:~$ sudo journalctl -u nginx -u apache2 --since "1 hour ago" | tail -n 40
Dec 30 09:08:11 server nginx[1490]: nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
Dec 30 09:08:11 server nginx[1490]: nginx: [emerg] still could not bind()
Dec 30 09:08:12 server systemd[1]: nginx.service: Failed with result 'exit-code'.
Dec 30 09:11:59 server systemd[1]: Started nginx.service - A high performance web server and a reverse proxy server.
Meaning: Nginx tried to bind 80 and lost at least once. Something changed later (maybe Apache stopped briefly, or Nginx config changed).
Decision: Fix port ownership so restarts are deterministic.
Task 4: Check what Apache is configured to listen on
cr0x@server:~$ sudo grep -R "^[[:space:]]*Listen" -n /etc/apache2/ports.conf /etc/apache2/sites-enabled/*.conf
/etc/apache2/ports.conf:5:Listen 80
/etc/apache2/ports.conf:8:Listen 443
Meaning: Apache is explicitly listening on both 80 and 443.
Decision: If Nginx is the edge, remove these and move Apache to loopback:8080 (and possibly 127.0.0.1:8443 if you insist on TLS internally).
Task 5: Check what Nginx is configured to listen on
cr0x@server:~$ sudo nginx -T 2>/dev/null | grep -R --line-number -E "listen\s+(80|443)" /etc/nginx/sites-enabled /etc/nginx/conf.d
/etc/nginx/sites-enabled/default:22: listen 80 default_server;
/etc/nginx/sites-enabled/default:23: listen [::]:80 default_server;
/etc/nginx/sites-enabled/default:30: listen 443 ssl default_server;
/etc/nginx/sites-enabled/default:31: listen [::]:443 ssl default_server;
Meaning: Nginx is also configured for both ports as default server. Classic collision.
Decision: Keep Nginx on 80/443; move Apache off those ports.
Task 6: Detect an accidental proxy loop in Nginx
cr0x@server:~$ sudo grep -R --line-number -E "proxy_pass\s+http://(localhost|127\.0\.0\.1)(:80)?/?;" /etc/nginx/sites-enabled
/etc/nginx/sites-enabled/app.conf:41: proxy_pass http://localhost;
Meaning: If Nginx listens on 80 and proxies to http://localhost (which defaults to port 80), it’s proxying to itself. That’s a loop generator.
Decision: Proxy to a backend port like 127.0.0.1:8080, or to a Unix socket, or to an upstream service.
Task 7: Detect an accidental proxy loop in Apache
cr0x@server:~$ sudo grep -R --line-number -E "ProxyPass\s+/" /etc/apache2/sites-enabled
/etc/apache2/sites-enabled/000-default.conf:18:ProxyPass / http://127.0.0.1:80/
/etc/apache2/sites-enabled/000-default.conf:19:ProxyPassReverse / http://127.0.0.1:80/
Meaning: Apache is proxying “/” to 127.0.0.1:80. If Apache itself owns 80, that’s recursion.
Decision: Either remove Apache proxying or ensure the target is a different port/service.
Task 8: Follow redirects and see the loop with curl
cr0x@server:~$ curl -I -L --max-redirs 10 http://example.internal/
HTTP/1.1 301 Moved Permanently
Server: nginx
Location: https://example.internal/
HTTP/2 301
server: nginx
location: http://example.internal/
curl: (47) Maximum (10) redirects followed
Meaning: HTTP redirects to HTTPS, then HTTPS redirects back to HTTP. That’s almost always an “X-Forwarded-Proto” or backend scheme detection issue.
Decision: Ensure one component is responsible for redirects, and the backend understands the original scheme.
Task 9: Confirm the Host header and forwarding headers from the edge
cr0x@server:~$ sudo nginx -T 2>/dev/null | sed -n '1,120p'
# configuration file /etc/nginx/nginx.conf:
user www-data;
worker_processes auto;
...
cr0x@server:~$ sudo grep -R --line-number -E "proxy_set_header\s+(Host|X-Forwarded-Proto|X-Forwarded-For)" /etc/nginx/sites-enabled
/etc/nginx/sites-enabled/app.conf:35: proxy_set_header Host $host;
/etc/nginx/sites-enabled/app.conf:36: proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
/etc/nginx/sites-enabled/app.conf:37: proxy_set_header X-Forwarded-Proto $scheme;
Meaning: These headers are present. Good. If missing, backends often mis-detect scheme/host and redirect.
Decision: If redirect loops persist, confirm the backend trusts these headers and isn’t overriding them.
Task 10: Verify Apache vhost bindings after changes (sanity check)
cr0x@server:~$ sudo apachectl -S
VirtualHost configuration:
*:8080 is a NameVirtualHost
default server app.internal (/etc/apache2/sites-enabled/001-app.conf:1)
port 8080 namevhost app.internal (/etc/apache2/sites-enabled/001-app.conf:1)
ServerRoot: "/etc/apache2"
Main DocumentRoot: "/var/www/html"
Main ErrorLog: "/var/log/apache2/error.log"
Meaning: Apache is now bound to 8080 for vhosts, not 80/443. That’s what you want when Nginx is in front.
Decision: Proceed to restart services and validate end-to-end.
Task 11: Validate Nginx config and reload without guessing
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
Meaning: Syntax is OK. This doesn’t prove your proxy target is correct, but it prevents “reload broke prod” mistakes.
Decision: Reload Nginx only after a successful test.
Task 12: Confirm backend reachability directly (bypass proxy)
cr0x@server:~$ curl -sS -D- http://127.0.0.1:8080/ -o /dev/null | head
HTTP/1.1 200 OK
Date: Tue, 30 Dec 2025 09:20:17 GMT
Server: Apache/2.4.58 (Ubuntu)
Content-Type: text/html; charset=UTF-8
Meaning: Apache backend is reachable on 8080 and returns 200. Good baseline.
Decision: If this fails, fix the backend first. Don’t debug the proxy while the backend is down.
Task 13: Confirm edge behavior (Nginx on 80/443)
cr0x@server:~$ curl -sS -D- http://127.0.0.1/ -o /dev/null | head
HTTP/1.1 301 Moved Permanently
Server: nginx
Location: https://app.internal/
cr0x@server:~$ curl -k -sS -D- https://127.0.0.1/ -o /dev/null | head
HTTP/2 200
server: nginx
content-type: text/html; charset=UTF-8
Meaning: Nginx is the edge. HTTP redirects to HTTPS; HTTPS returns 200.
Decision: If HTTPS returns 301 back to HTTP, fix forwarded proto / backend redirect logic.
Task 14: Verify no other process is squatting on ports
cr0x@server:~$ sudo lsof -nP -iTCP:80 -sTCP:LISTEN
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
nginx 1550 root 6u IPv4 29826 0t0 TCP *:80 (LISTEN)
nginx 1550 root 7u IPv6 29827 0t0 TCP *:80 (LISTEN)
Meaning: Only Nginx listens on 80. That’s the “boring” state you want.
Decision: If you see Apache or something else here, stop and re-check configs and enabled units.
Corriger proprement la liaison de ports (Apache ou Nginx, choisissez le propriétaire)
La solution n’est pas « redémarrer jusqu’à ce que ça marche ». La solution est : un service possède les ports publics, l’autre non.
Ci‑dessous deux solutions propres. Choisissez-en une. Engagez‑vous.
Solution 1 (recommandée pour les piles mixtes) : Nginx possède 80/443, Apache se déplace sur 127.0.0.1:8080
C’est le schéma de production le plus courant quand vous avez besoin d’Apache pour des raisons applicatives (réécritures legacy, modules, .htaccess)
mais préférez Nginx pour le comportement edge du proxy.
Étape A : Déplacer Apache hors de 80/443
Éditez /etc/apache2/ports.conf pour lier Apache uniquement au loopback :
cr0x@server:~$ sudo sed -n '1,120p' /etc/apache2/ports.conf
# If you just change the port or add more ports here, you will likely also
# have to change the VirtualHost statement in
# /etc/apache2/sites-enabled/000-default.conf
Listen 80
Listen 443
Remplacez par :
cr0x@server:~$ sudo bash -lc 'cat > /etc/apache2/ports.conf <<EOF
Listen 127.0.0.1:8080
<IfModule ssl_module>
Listen 127.0.0.1:8443
</IfModule>
<IfModule mod_gnutls.c>
Listen 127.0.0.1:8443
</IfModule>
EOF'
Pourquoi loopback ? Parce que cela empêche Apache d’être accessible directement depuis le réseau.
Cela élimine toute une catégorie de bugs « contourner le proxy » et de surprises de sécurité.
Étape B : Mettre à jour les vhosts Apache pour correspondre au nouveau port
Vos sites activés contiennent probablement <VirtualHost *:80>. Changez‑les en 127.0.0.1:8080 ou *:8080 selon la préférence.
J’aime le loopback explicite pour éviter les erreurs.
cr0x@server:~$ sudo grep -R --line-number "<VirtualHost" /etc/apache2/sites-enabled
/etc/apache2/sites-enabled/000-default.conf:1:<VirtualHost *:80>
cr0x@server:~$ sudo sed -i 's/<VirtualHost \*:80>/<VirtualHost 127.0.0.1:8080>/' /etc/apache2/sites-enabled/000-default.conf
Si vous utilisiez des vhosts TLS Apache (*:443), décidez si vous en avez encore besoin.
Dans un design typique « Nginx termine TLS », vous désactivez complètement les vhosts TLS Apache et gardez Apache HTTP uniquement en interne.
Étape C : Désactiver SSL d’Apache et les écouteurs 443 si Nginx termine TLS
cr0x@server:~$ sudo a2dismod ssl
Module ssl disabled.
To activate the new configuration, you need to run:
systemctl restart apache2
Meaning: Apache won’t load mod_ssl. If you still need internal TLS, keep it enabled and use 8443 loopback, but be consistent.
Decision: Most teams: terminate TLS at Nginx, run plain HTTP to Apache on 127.0.0.1:8080.
Étape D : Redémarrer Apache, puis vérifier qu’il ne lie plus 80/443
cr0x@server:~$ sudo systemctl restart apache2
cr0x@server:~$ sudo ss -ltnp '( sport = :80 or sport = :443 or sport = :8080 )'
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 511 127.0.0.1:8080 0.0.0.0:* users:(("apache2",pid=2012,fd=3))
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=1550,fd=6))
LISTEN 0 511 0.0.0.0:443 0.0.0.0:* users:(("nginx",pid=1550,fd=7))
Meaning: Perfect separation. Nginx is the edge. Apache is internal.
Decision: Now configure Nginx upstream to 127.0.0.1:8080.
Étape E : Configurer correctement le reverse proxy Nginx
Dans votre site Nginx, votre bloc proxy devrait ressembler à ceci (l’important est le port backend et les en‑têtes) :
cr0x@server:~$ sudo bash -lc 'cat > /etc/nginx/sites-available/app.conf <<EOF
server {
listen 80;
listen [::]:80;
server_name app.internal;
return 301 https://\$host\$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name app.internal;
ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host \$host;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
}
}
EOF'
cr0x@server:~$ sudo ln -sf /etc/nginx/sites-available/app.conf /etc/nginx/sites-enabled/app.conf
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
Solution 2 : Apache possède 80/443, supprimer Nginx de l’équation
C’est la bonne décision lorsque Nginx a été installé « parce que quelqu’un a dit que c’était plus rapide »
et que personne ne l’utilise réellement de façon intentionnelle.
Étape A : Arrêter et désactiver Nginx
cr0x@server:~$ sudo systemctl stop nginx
cr0x@server:~$ sudo systemctl disable --now nginx
Removed "/etc/systemd/system/multi-user.target.wants/nginx.service".
Meaning: Nginx won’t start on reboot or by default targets.
Decision: If you never needed Nginx, keep it off. Less moving parts, fewer 3 a.m. surprises.
Étape B : Confirmer qu’Apache se lie aux ports publics
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:(("apache2",pid=1421,fd=4))
LISTEN 0 511 0.0.0.0:443 0.0.0.0:* users:(("apache2",pid=1421,fd=5))
Meaning: Apache is the edge now.
Decision: Remove any reverse proxy configuration that was only needed because Nginx existed, and validate redirects/certs in Apache.
Éliminer les boucles de proxy et de redirection (définitivement)
Les conflits de ports sont bruyants. Les boucles de proxy sont furtives.
Le système peut être « up » alors que les utilisateurs tournent en rond ou que le trafic brûle votre CPU avec une récursion inutile.
Boucles de proxy : la mécanique
Une boucle de proxy se produit quand un proxy transfère une requête vers une adresse qui renvoie au proxy lui‑même.
Sur un hôte unique, c’est généralement localhost ou 127.0.0.1 sur le même port que celui sur lequel le proxy écoute.
Exemple de mauvaise configuration Nginx :
listen 80; et proxy_pass http://localhost;.
Cela dit : « Recevoir la requête sur 80, envoyer la requête sur 80. » La seule chose qui manque est une lettre d’excuse.
Boucles de redirection : le coupable habituel
Les boucles de redirection sont le plus souvent une « confusion de schéma ». Votre backend voit HTTP (parce que le proxy parle HTTP avec lui),
tandis que les utilisateurs sont en HTTPS (parce que le proxy termine TLS). Si le backend force HTTPS par redirection,
il envoie une redirection vers HTTPS. Le proxy reçoit cette requête et renvoie à nouveau HTTP au backend. Répétez jusqu’à ce que le navigateur abandonne.
Corriger la confusion de schéma : faites trois choses, pas une
- Envoyez
X-Forwarded-Protodepuis le proxy. Nginx :proxy_set_header X-Forwarded-Proto $scheme; - Faites confiance aux en‑têtes du proxy côté backend. Beaucoup de frameworks exigent une configuration explicite de « trusted proxies ».
- Assurez‑vous que les redirections se produisent à un seul endroit. Habituellement le proxy edge, car il sait ce que le client a utilisé.
Tester les boucles intentionnellement avec curl
Ne testez pas en rafraîchissant un navigateur et en plissant les yeux. Utilisez curl -I -L et limitez les redirections.
Vous voulez voir une chaîne courte et rationnelle : peut‑être un seul 301 de HTTP→HTTPS, puis 200.
cr0x@server:~$ curl -I -L --max-redirs 5 http://app.internal/ | sed -n '1,20p'
HTTP/1.1 301 Moved Permanently
Server: nginx
Location: https://app.internal/
HTTP/2 200
server: nginx
Meaning: One redirect, then success. That’s clean.
Decision: If you see alternating http/https locations, fix forwarded proto and backend redirect logic.
Deuxième petite blague (et nous avons fini avec les blagues) : une boucle de redirection, c’est comme une réunion d’entreprise — tout le monde « s’aligne » et rien n’est livré.
Trois mini-histoires d’entreprise (parce que la production a une mémoire)
1) Incident causé par une mauvaise hypothèse : « localhost signifie le backend »
Une entreprise de taille moyenne exploitait un seul hôte Ubuntu pour un outil interne. Ils ont ajouté Nginx « juste pour TLS » devant Apache.
Un ingénieur a copié un snippet de proxy depuis un vieux wiki : proxy_pass http://localhost;. Cela semblait inoffensif.
L’hypothèse : localhost équivaut à Apache.
Mais Apache écoutait toujours sur le port 80, et Nginx aussi. Après une séquence de redémarrage, Nginx est devenu l’écouteur sur 80.
Maintenant localhost:80 signifiait Nginx lui‑même. La boucle n’était pas immédiatement évidente parce que les checks de santé étaient naïfs :
ils tapaient / et acceptaient un 301.
Sous charge, la boucle s’est manifestée par des pics CPU et des accumulations de connexions. Les workers Nginx n’étaient pas « lents » ; ils parlaient à eux‑mêmes.
Les logs Apache semblaient calmes, car Apache ne faisait rien. L’astreinte a vu des 502 et a commencé à monter la VM,
ce qui n’a rien amélioré sauf la facture cloud.
La correction a pris cinq minutes une fois que quelqu’un a exécuté ss -ltnp et suivi les redirections avec curl.
La leçon n’était pas « ne pas utiliser localhost ». C’était : ne proxifiez jamais vers le même port sur lequel vous écoutez, et précisez toujours les ports backend.
2) Optimisation qui s’est retournée contre eux : « Faisons du HTTPS partout »
Une autre organisation exécutait Nginx en edge avec Apache derrière. Ils ont décidé de standardiser sur HTTPS et ont ajouté des règles de redirection dans les deux couches.
Nginx redirigeait HTTP vers HTTPS. Apache redirigeait aussi HTTP vers HTTPS, « au cas où ».
Personne n’a documenté la source de vérité prévue.
Ça fonctionnait en staging où tout était HTTP. En production, TLS se terminait chez Nginx.
Nginx parlait HTTP à Apache. Apache voyait une requête HTTP et faisait sa redirection « utile » vers HTTPS.
Il ne connaissait pas le schéma original du client. Il savait juste ce qu’il voyait.
L’URL de redirection qu’il générait renvoyait le client à Nginx, qui renvoyait encore HTTP à Apache.
Boucle infinie. Le monitoring montrait une montée des réponses 301, ce qui paraissait « sain » si vous ne regardiez que les codes d’état.
Les utilisateurs étaient exclus de l’application, et les tickets support montraient le classique « trop de redirections ».
Le rollback a été simple : supprimer l’application de l’enforcement HTTPS côté Apache et compter sur Nginx pour les redirections.
Ensuite ils ont ajouté X-Forwarded-Proto et configuré l’application pour lui faire confiance,
ainsi toutes les URLs générées par l’application étaient correctes. L’« optimisation » n’était pas d’appliquer HTTPS ; c’était de l’appliquer deux fois.
3) Pratique ennuyeuse mais correcte qui a sauvé la mise : lier le backend au loopback et documenter les ports
Une équipe proche de la finance avait la réputation d’être lente. Ils avaient aussi moins d’incidents, agaçant.
Leur règle : les services edge lient les ports publics ; les backends se lient à 127.0.0.1 ou à un VLAN privé seulement. Toujours.
Ils gardaient aussi un petit /etc/services.d/README décrivant quel démon possédait quels ports.
Lors d’une mise à jour de l’OS, une dépendance de paquet a tiré Apache.
Il a activé un site par défaut automatiquement. Sur beaucoup de systèmes cela aurait provoqué une panne : nouveau démon, nouveaux ports, écouteurs surprises.
Chez eux, Apache n’a pas pu se lier à 80/443 parce que Nginx les possédait déjà, et Apache était configuré (par politique) pour ne lier que le loopback.
L’effet net a été un simple avertissement dans journald et zéro impact client.
L’astreinte a enquêté le lendemain matin avec un café au lieu de l’adrénaline.
Ils ont désactivé Apache et ont poursuivi.
La morale est douloureusement peu sexy : des politiques ennuyeuses de liaison de ports préviennent des incidents excitants. Les incidents excitants n’impressionnent pas les auditeurs.
Erreurs fréquentes : symptôme → cause racine → correction
1) Nginx n’arrive pas à démarrer : « Address already in use »
Symptôme : nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
Cause racine : Apache (ou un autre service) écoute déjà sur 80/443 (éventuellement uniquement IPv6).
Correction : Décidez du propriétaire du port ; déplacez l’autre démon sur 127.0.0.1:8080 ou désactivez‑le. Vérifiez avec ss -ltnp.
2) Apache n’arrive pas à démarrer : erreurs AH00072
Symptôme : Les logs Apache affichent AH00072 et le service ne démarre pas.
Cause racine : Nginx écoute déjà sur les ports configurés pour Apache.
Correction : Même discipline : un seul edge. Rebindez Apache dans /etc/apache2/ports.conf et mettez à jour les vhosts, ou désactivez Nginx.
3) Navigateur : ERR_TOO_MANY_REDIRECTS
Symptôme : Le navigateur signale une boucle de redirection ; curl montre des locations alternant http/https.
Cause racine : Double enforcement HTTPS, ou backend n’utilisant pas X-Forwarded-Proto, ou URL de base de l’app incorrecte.
Correction : Faites les redirections dans une seule couche (généralement l’edge). Définissez les en‑têtes de forwarding. Configurez le backend/l’app pour faire confiance au proxy et générer le bon schéma d’URL.
4) Nginx 502 Bad Gateway
Symptôme : Nginx retourne 502 ; le log d’erreur mentionne connect() failed ou upstream prematurely closed connection.
Cause racine : Backend down, mauvais port, mauvais protocole (proxy HTTP vers backend HTTPS ou inverse), ou liaison IPv6/IPv4 incompatible.
Correction : Testez le backend directement avec curl ; vérifiez les sockets d’écoute ; corrigez la cible de proxy_pass ; assurez‑vous que le backend se lie à 127.0.0.1 si local.
5) « Ça marche en localhost mais pas depuis l’extérieur »
Symptôme : curl local fonctionne ; distant échoue ou atteint une page par défaut.
Cause racine : Service lié uniquement au loopback ou uniquement à IPv6 ; règles de firewall ; vhost par défaut qui capte l’en‑tête Host non apparié.
Correction : Vérifiez les liaisons avec ss ; vérifiez le routage d’Host avec curl -H "Host: ..." ; ajustez server_name/ServerName et default_server.
6) Mauvais site servi (page par défaut)
Symptôme : Vous obtenez « Apache2 Ubuntu Default Page » ou la page de bienvenue Nginx.
Cause racine : Site par défaut activé et prioritaire ; mismatch server_name ; SNI incorrect sur TLS.
Correction : Désactivez les defaults, assurez une ordre de vhost explicite, définissez explicitement server_name/ServerName, validez SNI avec curl et dumps de vhost.
Checklists / plan étape par étape
Checklist A : « Je veux que ce soit réparé » (Nginx edge, Apache backend)
- Exécutez
ss -ltnppour 80/443 et notez les propriétaires. - Déplacez Apache vers
127.0.0.1:8080dans/etc/apache2/ports.conf. - Mettez à jour les vhosts Apache de
*:80à127.0.0.1:8080. - Désactivez les vhosts SSL Apache sauf si vous avez besoin de TLS interne.
- Redémarrez Apache, confirmez qu’il n’écoute que sur 8080.
- Configurez Nginx pour écouter sur 80/443 et proxyfier vers
http://127.0.0.1:8080. - Assurez‑vous que Nginx définit
Host,X-Forwarded-For,X-Forwarded-Proto. - Testez le backend directement (curl vers 127.0.0.1:8080), puis testez l’edge (curl vers 127.0.0.1:80/443).
- Suivez les redirections avec curl et limitez-les. Maximum une redirection pour HTTP→HTTPS.
- Ce n’est qu’alors que vous exposez au trafic réel.
Checklist B : « Je veux un seul serveur web » (Apache seulement)
- Arrêtez et désactivez Nginx.
- Confirmez qu’Apache écoute sur 80/443 et sert le bon vhost.
- Retirez la logique de redirection héritée de Nginx si elle est redondante.
- Validez les certificats TLS et la sélection de vhost.
- Confirmez qu’il n’y a pas de règles de firewall restantes qui attendaient un comportement Nginx.
Checklist C : Tests de régression après toute modification
- Ports : vérifiez les écouteurs sur 80/443/8080 avec
ss. - Syntaxe config :
nginx -t,apachectl configtest. - Test direct backend : curl du backend directement.
- Test edge : curl edge HTTP et HTTPS, suivez les redirections, confirmez le statut final.
- Routage Host : curl avec un en‑tête Host pour assurer la correspondance de vhost.
- Logs : vérifiez le log d’erreurs Nginx et le log d’erreurs Apache pour les régressions immédiates.
cr0x@server:~$ sudo apachectl configtest
Syntax OK
Meaning: Apache config parses. It doesn’t mean your vhosts are correct, but it reduces “reload roulette.”
FAQ (les questions que vous poserez à 2h du matin)
1) Apache et Nginx peuvent-ils tourner sur le même serveur ?
Oui. Ils ne peuvent juste pas écouter tous les deux sur le même IP:port. Choisissez un edge (80/443) et déplacez l’autre sur le loopback ou un port différent.
2) Pourquoi Nginx dit que le port 80 est utilisé alors que je ne vois pas Apache sur IPv4 ?
Parce que quelque chose peut écouter sur IPv6 ([::]:80). ss -ltnp montrera les deux piles.
Corrigez en changeant l’écouteur ou en désactivant l’autre service.
3) Dois‑je proxifier vers « localhost » ou « 127.0.0.1 » ?
Utilisez 127.0.0.1:8080 (port explicite) ou une socket Unix. « localhost » sans port invite à l’ambiguïté et aux boucles.
4) Pourquoi j’obtiens la page par défaut Apache/Nginx au lieu de mon application ?
Votre requête arrive dans un vhost par défaut (mauvais server_name/ServerName, mauvais ordre, ou Host header non apparié).
Désactivez les sites par défaut et rendez les noms d’hôte explicites.
5) Qu’est‑ce qui provoque des boucles HTTP↔HTTPS derrière un reverse proxy ?
Habituellement une incompatibilité de schéma. Le backend voit HTTP et force HTTPS, mais le proxy a déjà fait HTTPS et transmet HTTP en interne.
Corrigez en définissant X-Forwarded-Proto et en faisant confiance à ce header côté backend ; faites les redirections dans une seule couche.
6) Si Nginx termine TLS, Apache doit‑il encore écouter sur 443 ?
Typiquement non. Gardez Apache HTTP uniquement en interne sur 127.0.0.1:8080. Le TLS interne est un choix valide, mais il doit être intentionnel et cohérent.
7) Comment confirmer que le proxy envoie bien l’en‑tête Host ?
Vérifiez la config Nginx pour proxy_set_header Host $host;. Puis vérifiez le comportement du backend en faisant un curl via Nginx avec différents Host headers.
cr0x@server:~$ curl -sS -H "Host: app.internal" http://127.0.0.1/ -o /dev/null -D- | head
HTTP/1.1 301 Moved Permanently
Server: nginx
Location: https://app.internal/
8) Quelle est la façon la plus propre d’assurer qu’Apache n’est pas accessible directement ?
Liegez Apache à 127.0.0.1 seulement. Ne comptez pas sur des règles de firewall comme première ligne de défense ; rendez la socket inaccessible depuis le réseau.
9) Les unités socket systemd changent‑elles cette histoire ?
Elles peuvent. L’activation par socket peut garder des ports ouverts indépendamment du processus de service.
Dans le monde Apache/Nginx sur Ubuntu, vous verrez habituellement des services classiques, mais vérifiez toujours avec ss et systemctl list-sockets.
cr0x@server:~$ systemctl list-sockets --no-pager | head
LISTEN UNIT ACTIVATES
/dev/log systemd-journald.socket systemd-journald.service
...
10) J’ai corrigé les ports, mais j’ai encore des 502. Et maintenant ?
Confirmez la joignabilité et le protocole du backend. Curllez le backend directement. Puis consultez les logs d’erreur Nginx pour des upstream connect errors.
502 signifie généralement « impossible de joindre l’upstream » ou « l’upstream a répondu quelque chose d’inattendu ».
cr0x@server:~$ sudo tail -n 20 /var/log/nginx/error.log
2025/12/30 09:24:08 [error] 1552#1552: *12 connect() failed (111: Connection refused) while connecting to upstream, client: 10.0.2.15, server: app.internal, request: "GET / HTTP/2.0", upstream: "http://127.0.0.1:8080/", host: "app.internal"
Conclusion : prochaines étapes pour éviter ce désordre
La correction du conflit Apache vs Nginx sur Ubuntu 24.04 n’est pas une question d’ingéniosité. C’est une question de propriété.
Décidez qui possède 80/443. Liegez le backend sur le loopback. Rendez les cibles de proxy explicites. Puis testez avec curl sérieusement.
Prochaines étapes pratiques :
- Notez votre architecture prévue (Nginx edge → Apache 8080, ou Apache seul, ou Nginx seul).
- Faites respecter la propriété des ports dans la configuration, pas dans la mémoire tribale.
- Ajoutez un petit test smoke après changement : écouteurs
ss+ chaîne de redirections curl + curl direct backend. - Désactivez les sites par défaut que vous n’utilisez pas. Les defaults sont amicaux… jusqu’à ce qu’ils ne le soient plus.
- Conservez les redirections dans une seule couche, et soyez explicite sur les headers forwardés.
Si vous faites cela, le cas n°34 reste résolu. Définitivement. Le meilleur type d’incident est celui que vous ne subissez plus.