Debian 13 : Corriger « Trop de redirections » dans Nginx en résolvant les boucles canonique et HTTPS (cas n°71)

Cet article vous a aidé ?

Vous avez modifié « une petite redirection » et soudain tous les navigateurs crient « Trop de redirections ». Le site donne l’impression de se téléporter entre HTTP et HTTPS, ou entre www et l’apex, jusqu’à ce que le navigateur abandonne. Les logs Nginx semblent innocents. Votre load balancer jure qu’il n’est pas impliqué. Et tout le monde veut que ce soit réparé avant la prochaine réunion.

Ceci est le cas n°71 : la boucle canonique/HTTPS. C’est ennuyeux, fréquent et parfaitement évitable — si vous cessez de deviner et commencez à valider ce que le client voit réellement, ce que Nginx croit voir, et ce que fait votre application en amont.

Ce que « Trop de redirections » signifie réellement en termes Nginx

Les navigateurs suivent automatiquement les redirections HTTP. Ils en suivront beaucoup — jusqu’à un certain point. Quand vous voyez « Trop de redirections », ce n’est pas un jugement moral. C’est une boucle : la requête A provoque une redirection vers B ; la requête B provoque une redirection vers A (ou vers C, qui finit par revenir à A). Le navigateur s’arrête pour éviter un ping-pong infini.

Dans l’univers Nginx, une boucle provient généralement d’un de ces schémas :

  • Boucle de schéma : HTTP → HTTPS → HTTP (souvent une confusion d’en-têtes proxy).
  • Boucle d’hôte canonique : example.comwww.example.comexample.com (deux règles de redirection en désaccord).
  • Boucle de normalisation de chemin : /app/app//app (gestion des slashs entre Nginx, l’application et l’amont).
  • Boucle de port : la redirection inclut explicitement :443 ou :80 et quelque chose « corrige » cela en retour.
  • Couches mixtes : redirections CDN/LB plus redirections Nginx plus redirections applicatives.

Conseil d’opinion : si vous avez à la fois Nginx et l’application qui font de la canonicalisation, choisissez-en un. Les redirections qui s’opposent sont comme des tableurs de « source de vérité » qui s’affrontent : tout le monde perd.

Une réalité opérationnelle : les boucles de redirection sont diagnostiquables en quelques minutes avec un terminal et de la discipline. La correction la plus rapide n’est pas « essayer une autre réécriture ». La correction la plus rapide est de collecter la chaîne de redirections, identifier l’attribut qui bascule (schéma/hôte/chemin), puis supprimer l’une des deux règles concurrentes.

Faits et contexte qui rendent ce problème moins mystérieux

  • Les redirections HTTP sont anciennes. Les codes 301/302 remontent aux premières spécifications HTTP ; « Moved Permanently » précède la plupart des piles web actuelles.
  • 301 est devenu une arme de cache. Les navigateurs et intermédiaires peuvent mettre en cache les 301 de façon agressive ; le débogage devient « je l’ai corrigé mais mon portable ne l’a pas encore ».
  • 307/308 existent pour une raison. Le 302 changeait historiquement POST en GET chez certains clients ; 307/308 préservent la sémantique de la méthode plus uniformément.
  • Le return de Nginx est plus sûr que rewrite. L’ancien moteur de réécriture est puissant, mais il est facile de créer des boucles ou de continuer à réécrire en interne par accident.
  • Les redirections d’hôte canonique ont commencé comme hygiène SEO. Les moteurs de recherche pénalisaient le contenu dupliqué ; les équipes ops ont hérité de la douleur lorsque le SEO et TLS se sont superposés.
  • Les proxys ont changé la notion de « HTTPS ». Si le TLS se termine sur un load balancer, Nginx voit du HTTP brut à moins que vous ne lui indiquiez X-Forwarded-Proto.
  • HSTS a augmenté les enjeux. Une fois HSTS activé, les clients tenteront HTTPS quoi qu’il arrive ; une redirection HTTPS cassée devient immédiatement visible pour l’utilisateur.
  • Les CDN aiment « aider ». Beaucoup de CDN peuvent forcer HTTPS ou réécrire les hôtes. C’est génial jusqu’à ce que Nginx le fasse aussi.

Plan de diagnostic rapide (premier/deuxième/troisième)

Premier : capturez la chaîne de redirections exactement comme le client la voit

  1. Utilisez curl -I -L et enregistrez Location, les codes d’état et si l’hôte/le schéma changent à chaque saut.
  2. Vérifiez si la boucle alterne entre HTTP/HTTPS ou entre hôtes (apex vs www).
  3. Confirmez si la redirection provient de Nginx ou d’un élément en amont en regardant les en-têtes de réponse (Server, en-têtes personnalisés, ou un en-tête de traçage que vous ajoutez).

Second : vérifiez ce que Nginx pense être la requête

  1. Inspectez la config Nginx pour return 301, rewrite, blocs if, et blocs server dupliqués pour le même nom.
  2. Consultez les logs d’accès avec $scheme, $host et $http_x_forwarded_proto (ajoutez temporairement un format de log diagnostic si nécessaire).
  3. Si vous êtes derrière un proxy/CDN, confirmez si vous ne faites confiance aux en-têtes transférés qu’aux adresses IP connues.

Troisième : isolez les couches

  1. Contournez le CDN/LB si possible (directement vers l’IP d’origine avec un en-tête Host).
  2. Désactivez temporairement les redirections canoniques de l’application ou définissez explicitement une URL de base pour qu’elle corresponde à la politique Nginx.
  3. Re-testez et arrêtez-vous uniquement quand la chaîne contient au plus une redirection (idéalement zéro pour des requêtes déjà canoniques).

Idée paraphrasée (attribuée) : « L’espoir n’est pas une stratégie. » — idée souvent attribuée au leadership fiabilité et opérations. Traitez les redirections de la même manière : validez, ne vous fiez pas à l’intuition.

Anatomie d’une redirection : hôte canonique, schéma et chemin

Décision d’hôte canonique (choisissez-en un et appliquez-la une seule fois)

Hôte canonique signifie que vous décidez si le site « vit » sur example.com ou sur www.example.com. L’un ou l’autre est acceptable ; l’indécision ne l’est pas. Appliquez-le à une couche — de préférence à la périphérie (Nginx) car c’est peu coûteux et cohérent.

Deux règles canoniques qui s’opposent sont la boucle classique :

  • Nginx force le www.
  • L’application force l’apex (ou un CDN le fait).
  • Résultat : rebond infini.

Décision de schéma canonique (HTTPS, et soyez honnête à ce sujet)

Si vous terminez TLS sur Nginx, $scheme est réel. Si TLS se termine avant Nginx, $scheme ment (il sera http), à moins que vous ne définissiez et ne fassiez confiance à X-Forwarded-Proto ou à l’en-tête standardisé Forwarded. Beaucoup d’extraits « redirection HTTPS » sur internet supposent que Nginx voit TLS. Cette hypothèse est la façon dont le cas n°71 arrive.

Décision de normalisation de chemin (slashs et fichiers index)

Les boucles de chemin surviennent quand plusieurs composants « normalisent » différemment. Nginx peut rediriger /app vers /app/ à cause de try_files ou de autoindex, tandis que l’application redirige vers /app parce qu’elle pense que les routes ne doivent pas se terminer par un slash. Choisissez la politique canonique et implémentez-la en un seul endroit.

Blague n°1 (courte, pertinente) : Les boucles de redirection sont juste des tests de charge que vous n’aviez pas budgétés.

Tâches pratiques : commandes, sorties et la décision que vous prenez

Ce ne sont pas des « exécuter ça et espérer ». Chaque tâche inclut ce qu’il faut chercher et la décision qu’elle oriente. Exécutez-les sur Debian 13, mais la logique est portable.

Tâche 1 : Reproduire avec une trace complète des redirections

cr0x@server:~$ curl -sS -D- -o /dev/null -L -I http://example.com/
HTTP/1.1 301 Moved Permanently
Server: nginx
Location: https://example.com/
HTTP/2 301
server: nginx
location: http://example.com/
HTTP/1.1 301 Moved Permanently
Server: nginx
Location: https://example.com/

Ce que cela signifie : Le schéma alterne HTTPS → HTTP → HTTPS. C’est une boucle. Si vous voyez des hôtes alternés à la place, vous avez une lutte d’hôte canonique.

Décision : Arrêtez de bidouiller les chemins. Allez directement à la logique schéma/canonique et vérifiez les en-têtes proxy.

Tâche 2 : Afficher uniquement les en-têtes Location (signature rapide de boucle)

cr0x@server:~$ curl -sS -I http://example.com/ | sed -n 's/^Location: //p'
https://example.com/

Ce que cela signifie : Le premier saut est HTTP → HTTPS. Correct en soi.

Décision : Testez maintenant le point HTTPS pour voir qui vous renvoie vers HTTP.

Tâche 3 : Inspecter la réponse HTTPS sans suivre les redirections

cr0x@server:~$ curl -sS -I https://example.com/ | sed -n '1p;/^Location:/p'
HTTP/2 301
location: http://example.com/

Ce que cela signifie : Quelque chose servant HTTPS redirige vers HTTP. Ce « quelque chose » peut être Nginx, l’application ou un proxy devant.

Décision : Identifiez quelle couche a émis cette réponse (en-têtes, logs et tests de contournement).

Tâche 4 : Confirmer quel Nginx répond (empreinte d’en-têtes)

cr0x@server:~$ curl -sS -I https://example.com/ | grep -iE '^(server:|via:|x-cache:|x-served-by:|cf-|x-amz-)'
server: nginx

Ce que cela signifie : Pas définitif, mais si vous voyez des en-têtes spécifiques au CDN, vous ne parlez pas directement à votre Nginx.

Décision : Si vous suspectez un intermédiaire, contournez-le ensuite.

Tâche 5 : Contourner DNS/CDN et atteindre l’origine par IP avec un en-tête Host

cr0x@server:~$ curl -sS -I --resolve example.com:443:203.0.113.10 https://example.com/ | sed -n '1p;/^location:/Ip'
HTTP/2 301
location: http://example.com/

Ce que cela signifie : Même en atteignant l’origine, HTTPS redirige vers HTTP. C’est probablement Nginx ou l’application en amont derrière Nginx.

Décision : Vérifiez la config Nginx et les redirections de l’application en amont.

Tâche 6 : Dumper la configuration Nginx active (arrêtez de deviner quel fichier est inclus)

cr0x@server:~$ sudo nginx -T 2>/dev/null | sed -n '1,80p'
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:
user www-data;
worker_processes auto;
...

Ce que cela signifie : Vous avez la config rendue incluant les fichiers include. C’est la vérité terrain.

Décision : Recherchez dans cette sortie les règles de redirection et les blocs server dupliqués.

Tâche 7 : Trouver les directives de redirection et les conditions « if » suspectes

cr0x@server:~$ sudo nginx -T 2>/dev/null | grep -nE 'return 30[12]|rewrite |if \(|server_name|listen 80|listen 443'
412:    listen 80;
417:    server_name example.com www.example.com;
420:    if ($scheme = http) { return 301 https://$host$request_uri; }
612:    listen 443 ssl http2;
618:    if ($scheme = https) { return 301 http://$host$request_uri; }

Ce que cela signifie : Vous avez littéralement des redirections opposées : HTTP→HTTPS dans un serveur, HTTPS→HTTP dans l’autre. C’est votre boucle.

Décision : Supprimez la redirection HTTPS→HTTP incorrecte. Remplacez par une politique canonique unique.

Tâche 8 : Vérifier où atterrissent les requêtes (quel bloc server) en utilisant les logs d’accès

cr0x@server:~$ sudo tail -n 3 /var/log/nginx/access.log
203.0.113.55 - - [30/Dec/2025:11:32:18 +0000] "GET / HTTP/1.1" 301 169 "-" "curl/8.5.0"
203.0.113.55 - - [30/Dec/2025:11:32:18 +0000] "GET / HTTP/2.0" 301 169 "-" "curl/8.5.0"
203.0.113.55 - - [30/Dec/2025:11:32:19 +0000] "GET / HTTP/1.1" 301 169 "-" "curl/8.5.0"

Ce que cela signifie : Même client, 301 répétés. Vous avez besoin de plus de contexte : host, scheme, et forwarded proto.

Décision : Ajoutez temporairement un format de log diagnostic qui imprime les variables importantes.

Tâche 9 : Ajouter un log_format temporaire pour exposer scheme/host/forwarded proto

cr0x@server:~$ sudo tee /etc/nginx/conf.d/zz-debug-logformat.conf >/dev/null <<'EOF'
log_format diag '$remote_addr host=$host scheme=$scheme '
               'xfp=$http_x_forwarded_proto uri=$request_uri '
               'status=$status loc=$sent_http_location';
access_log /var/log/nginx/access_diag.log diag;
EOF

Ce que cela signifie : Vous avez créé un log d’accès dédié au diagnostic sans toucher votre format principal.

Décision : Rechargez Nginx et exécutez un curl ; puis lisez le log diag.

Tâche 10 : Recharger Nginx en toute sécurité et confirmer que la config est valide

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 : Pas d’erreurs de syntaxe ; le reload a été appliqué.

Décision : Maintenant vous pouvez faire confiance au log diag pour refléter le comportement courant.

Tâche 11 : Générer une requête et lire le log de diagnostic

cr0x@server:~$ curl -sS -I https://example.com/ >/dev/null
cr0x@server:~$ sudo tail -n 1 /var/log/nginx/access_diag.log
203.0.113.55 host=example.com scheme=https xfp= uri=/ status=301 loc=http://example.com/

Ce que cela signifie : Nginx voit scheme=https (donc TLS est probablement sur Nginx), et renvoie pourtant une redirection vers http://. C’est une règle de configuration explicite, pas une confusion proxy.

Décision : Supprimez toute redirection HTTPS→HTTP. Si vous avez besoin d’HTTP en interne, gardez-le interne — ne rétrogradez pas les clients publics vers HTTP.

Tâche 12 : Si vous êtes derrière un proxy, vérifiez à quoi ressemble le forwarded proto

cr0x@server:~$ curl -sS -I --resolve example.com:80:203.0.113.10 http://example.com/ -H 'X-Forwarded-Proto: https' | sed -n '1p;/^Location:/p'
HTTP/1.1 301 Moved Permanently
Location: https://example.com/

Ce que cela signifie : Quand vous dites à Nginx que le schéma client était HTTPS, il choisit HTTPS. Bien : votre logique peut devenir consciente du proxy.

Décision : Implémentez correctement et en toute sécurité la gestion du forwarded-proto (faites confiance seulement aux IPs proxy connues).

Tâche 13 : Identifier qui écoute sur les ports 80/443 (éviter les services fantômes)

cr0x@server:~$ sudo ss -ltnp | grep -E ':(80|443)\s'
LISTEN 0      511          0.0.0.0:80        0.0.0.0:*    users:(("nginx",pid=1234,fd=6))
LISTEN 0      511          0.0.0.0:443       0.0.0.0:*    users:(("nginx",pid=1234,fd=7))

Ce que cela signifie : Nginx est le seul écouteur sur 80/443. Si vous aviez vu autre chose (Apache, un serveur dev), vous seriez en train de déboguer le mauvais processus.

Décision : Si les ports sont disputés, réglez cela d’abord. La logique de redirection est hors sujet si le mauvais daemon répond.

Tâche 14 : Inspecter les fichiers vhost spécifiques activés sur Debian

cr0x@server:~$ ls -l /etc/nginx/sites-enabled/
total 0
lrwxrwxrwx 1 root root 34 Dec 30 10:58 example.conf -> ../sites-available/example.conf

Ce que cela signifie : Vous avez un site activé. Si vous avez plusieurs fichiers avec des server_name qui se chevauchent, attendez-vous à des correspondances imprévisibles.

Décision : Assurez-vous qu’un seul bloc server canonique « possède » chaque nom d’hôte.

Tâche 15 : Tester la sélection de serveur de Nginx avec des en-têtes Host explicites

cr0x@server:~$ curl -sS -I http://203.0.113.10/ -H 'Host: example.com' | sed -n '1p;/^Location:/p'
HTTP/1.1 301 Moved Permanently
Location: https://example.com/
cr0x@server:~$ curl -sS -I http://203.0.113.10/ -H 'Host: www.example.com' | sed -n '1p;/^Location:/p'
HTTP/1.1 301 Moved Permanently
Location: https://www.example.com/

Ce que cela signifie : Les deux hôtes redirigent vers eux-mêmes en HTTPS. Si vous vouliez canoniser vers l’apex uniquement, c’est incorrect (mais pas forcément une boucle).

Décision : Décidez l’hôte canonique et appliquez-le explicitement (une redirection, pas deux mondes parallèles).

Tâche 16 : Valider que l’application n’émet pas ses propres redirections schéma/hôte

cr0x@server:~$ curl -sS -I http://127.0.0.1:8080/ | sed -n '1p;/^Location:/p;/^Server:/p'
HTTP/1.1 301 Moved Permanently
Server: gunicorn
Location: https://example.com/

Ce que cela signifie : Votre application en amont redirige vers HTTPS elle-même. Cela peut être acceptable, mais seulement si elle est d’accord avec Nginx. Si Nginx redirige aussi (ou pire, redirige à l’inverse), des boucles surviennent.

Décision : Choisissez : redirections canoniques dans Nginx ou dans l’application. Puis désactivez l’autre.

Patrons de correction qui tiennent (avec configuration Nginx correcte)

Patron A : TLS termine sur Nginx (le plus simple, le plus fiable)

C’est la configuration la plus propre : les clients se connectent à Nginx sur 443, et Nginx connaît le vrai schéma. Vos redirections peuvent utiliser $scheme en toute sécurité parce qu’il reflète la réalité.

Règles :

  • Serveur sur le port 80 : rediriger tout vers l’hôte HTTPS canonique.
  • Serveur sur le port 443 : servir le contenu ; éventuellement rediriger les hôtes non canoniques vers l’hôte canonique (toujours en HTTPS).
  • Ne jamais faire « si le schéma est https alors rediriger vers http ». Jamais. Si vous devez supporter HTTP en interne, faites-le sur un nom d’hôte ou un écouteur différent, pas en rétrogradant les utilisateurs publics.
cr0x@server:~$ sudo tee /etc/nginx/sites-available/example.conf >/dev/null <<'EOF'
# Canonical policy:
# - canonical host: example.com (no www)
# - canonical scheme: https
# - all HTTP requests redirect to https://example.com$request_uri
# - all HTTPS requests to www redirect to https://example.com$request_uri

server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    return 301 https://example.com$request_uri;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name example.com;

    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # Your normal site config:
    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name www.example.com;

    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    return 301 https://example.com$request_uri;
}
EOF

Pourquoi cela fonctionne : la décision canonique se produit exactement une fois pour chaque point d’entrée non canonique. Vous ne redirigez jamais du canonique → non-canonique.

Patron B : TLS termine en amont (load balancer/CDN), Nginx ne voit que HTTP

C’est là que les gens trébuchent. Nginx voit $scheme=http, parce que le LB se connecte à Nginx en HTTP. Si vous écrivez « if scheme is http redirect to https », vous venez de forcer le saut LB→origine à rediriger aussi. Selon la façon dont le LB gère cela, vous pouvez créer une boucle ou au moins des redirections inutiles.

Ce que vous voulez réellement : « Si le client a utilisé HTTP, rediriger vers HTTPS. » Ce « schéma client » doit provenir d’un en-tête de confiance.

Faites-le comme un adulte : faites confiance à X-Forwarded-Proto seulement depuis vos plages IP de proxy/LB connues, et utilisez une variable qui représente le schéma original.

cr0x@server:~$ sudo tee /etc/nginx/conf.d/forwarded-proto.conf >/dev/null <<'EOF'
# Trust X-Forwarded-Proto only from known proxies/LBs.
# Replace these with your actual proxy subnets.
set_real_ip_from 10.0.0.0/8;
set_real_ip_from 192.168.0.0/16;
real_ip_header X-Forwarded-For;
real_ip_recursive on;

# Derive a client-facing scheme.
map $http_x_forwarded_proto $client_scheme {
    default $scheme;
    https https;
    http  http;
}
EOF

Utilisez maintenant $client_scheme dans la logique de redirection au lieu de $scheme :

cr0x@server:~$ sudo tee /etc/nginx/sites-available/example.conf >/dev/null <<'EOF'
# Canonical policy behind a TLS-terminating proxy:
# - canonical host: example.com
# - canonical scheme: https (as seen by the client)
# - Nginx listens on 80 only (proxy-to-origin), but still enforces canonical policy
#   using X-Forwarded-Proto from trusted proxies.

server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    # Redirect non-https clients to https canonical.
    if ($client_scheme != "https") {
        return 301 https://example.com$request_uri;
    }

    # Redirect www to apex (still https).
    if ($host = "www.example.com") {
        return 301 https://example.com$request_uri;
    }

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $client_scheme;
    }
}
EOF

Oui, j’ai utilisé if à l’intérieur de server. C’est l’un des rares endroits où c’est acceptable. Évitez if à l’intérieur des blocs location pour des acrobaties de réécriture ; les redirections au niveau du serveur sont simples et prévisibles.

Règle stricte : si vous pouvez pousser les redirections canoniques vers la périphérie (LB/CDN), faites-le là-bas et désactivez-les dans Nginx. Mais ne répartissez pas la responsabilité entre les couches à moins que vous aimiez les appels d’urgence.

Patron C : Normalisation de chemin — stoppez le ping-pong des slashs

Si votre boucle fait basculer les slashs finaux, vous devez unifier la politique. Nginx peut l’imposer, mais si votre application l’impose aussi, choisissez-en une.

Un choix sûr courant est : « les répertoires se terminent par un slash, les fichiers non. » Nginx a déjà des opinions là-dessus. Si votre routeur applicatif déteste les slashs finaux, désactivez les redirections automatiques de répertoire de Nginx en alignant try_files et les routes, ou laissez l’application en être entièrement responsable.

Derrière un proxy/CDN : faire confiance aux en-têtes sans se mentir

Les en-têtes transférés sont à la fois nécessaires et dangereux. Nécessaires parce que votre serveur d’origine ne voit pas le TLS client. Dangereux parce que n’importe quel client peut envoyer X-Forwarded-Proto: https et piéger des configurations naïves pour générer des liens HTTPS, marquer des cookies sécurisés, ou éviter des redirections.

Rendez la « confiance » explicite

La confiance doit dépendre de l’IP source. Sur Debian 13, Nginx est généralement empaqueté avec des valeurs par défaut raisonnables, mais il ne devinera pas vos frontières réseau. Vous devez définir set_real_ip_from pour vos plages proxy réelles.

Sachez quel en-tête votre proxy émet

Beaucoup de systèmes utilisent X-Forwarded-Proto. Certains utilisent l’en-tête standardisé Forwarded. D’autres utilisent un en-tête spécifique au fournisseur. L’important n’est pas le nom ; c’est la cohérence dans la chaîne.

Évitez la surprise de la « redirection absolue »

Nginx peut générer des redirections absolues. Si vous fuyiez par accident des noms d’hôtes internes (comme origin.internal) dans l’en-tête Location, vos utilisateurs connaîtront votre cartographie réseau gratuitement. Ce n’est pas un prix que vous voulez gagner.

Si vous voyez des redirections vers le mauvais nom d’hôte, vous avez probablement utilisé $host alors que vous vouliez un domaine canonique fixe, ou votre proxy réécrit Host de manière inattendue.

Quand ce n’est pas Nginx : l’application qui vous combat

Certaines frameworks redirigeront vers une « URL de base », forcent HTTPS, ou imposent des règles de slash final. Si l’application tourne derrière un proxy et ne comprend pas les en-têtes transférés, elle peut penser que chaque requête est en HTTP et « l’améliorer » — tandis que Nginx (ou le proxy) la rétrograde, ou l’inverse.

Trois actions tactiques qui corrigent de vrais systèmes en production :

  • Définir explicitement l’URL externe de l’application (URL de base / URL publique). Beaucoup de systèmes ont une seule configuration pour cela, et cela évite la confusion hôte/schéma.
  • Assurez-vous que l’application respecte les en-têtes transférés uniquement depuis des IP proxy de confiance (même idée que pour Nginx).
  • Décidez qui possède les redirections. Si l’application en a besoin pour le routage, laissez l’application gérer les redirections de chemin et laissez Nginx s’occuper uniquement de la canonicalisation schéma/hôte — ou l’inverse. Ne dupliquez pas simplement.

Blague n°2 (courte, pertinente) : Si deux composants « appliquent des URL canoniques », ils finiront par être d’accord — juste après la fin de l’incident.

Trois mini-histoires d’entreprise depuis les tranchées des redirections

Mini-histoire 1 : Un incident causé par une mauvaise hypothèse

L’entreprise avait une configuration propre : un load balancer géré terminait TLS, puis transmettait le trafic à Nginx sur le port 80 dans un réseau privé. Quelqu’un a ajouté une règle « simple » dans Nginx : rediriger HTTP vers HTTPS. Ils l’ont testée en curlant l’origine directement en HTTP et ont vu le 301 attendu. Déployé.

En production, le load balancer se connectait à Nginx en HTTP (comme prévu). Nginx voyait $scheme=http pour chaque requête. Il a donc redirigé chaque requête vers HTTPS. Le load balancer a suivi les redirections dans ses checks de santé et a commencé à échouer car il ne pouvait pas négocier TLS vers l’origine qui ne parlait pas TLS. Le pool s’est vidé. Le site est tombé, même si « la redirection était correcte ».

La correction n’a pas été magique : supprimer les redirections de schéma sur l’origine, et appliquer HTTPS au niveau du load balancer. Si l’application de l’origine doit faire l’enforcement, utilisez X-Forwarded-Proto et faites-lui confiance seulement depuis le sous-réseau du balancer. La vraie leçon : ne pas écrire des redirections basées sur ce que l’origine voit si l’origine n’est pas le point de terminaison TLS.

Après, ils ont ajouté un contrôle de déploiement qui exécute curl -I contre le point public et le chemin de contournement de l’origine, et compare la chaîne de redirections. La prochaine fois que quelqu’un a essayé d’« améliorer » les redirections, le pipeline a détecté la divergence avant les clients.

Mini-histoire 2 : Une optimisation qui s’est retournée

Une autre organisation a essayé de réduire les sauts de redirection pour améliorer les performances. Leur objectif : « pas de redirections du tout ». Ils ont supprimé la redirection du port 80 et ont configuré le CDN pour faire la canonicalisation. Sur le papier, c’est plus propre : moins d’allers-retours, moins de charge pour l’origine et comportement cohérent globalement.

Puis ils ont déployé une seconde règle CDN : normaliser www vers l’apex. Pendant ce temps, l’application imposait toujours www car une intégration legacy l’attendait. Cela n’est pas apparu dans la surveillance de base parce que le site « chargeait » pour certains utilisateurs — selon l’état du cache et le nom d’hôte entré.

Le pire était l’intermittence. Le CDN a mis en cache des 301 dans certains POPs. Certains utilisateurs voyaient la boucle. D’autres non. Les équipes internes « ne pouvaient pas reproduire », ce qui est la langue corporate pour « j’ai essayé une fois et je me suis ennuyé ».

Ils ont récupéré la situation en déclarant une politique canonique et en l’implémentant en exactement un endroit : le CDN. Puis ils ont désactivé l’application pour l’enforcement d’hôte et défini l’URL de base de l’application sur le domaine canonique. Les performances se sont améliorées et sont restées, parce que la politique de redirection a cessé d’osciller entre les couches.

Mini-histoire 3 : Une pratique ennuyeuse mais correcte qui a sauvé la mise

Une équipe paiements avait une règle : chaque changement de comportement en périphérie nécessitait un « snapshot de chaîne de redirection » attaché à la demande de changement. C’était ennuyeux. Les gens se plaignaient. Mais cela forçait la clarté : que se passe-t-il pour HTTP apex, HTTP www, HTTPS apex, HTTPS www, et un chemin bizarre avec une query string.

Pendant une mise à jour régulière de Debian 13, la config Nginx a été refactorisée. Un nouvel ingénieur a dupliqué involontairement un server_name dans deux fichiers vhost activés. Nginx n’a pas généré d’erreur ; il a juste choisi un bloc server pour certaines requêtes selon la précédence des correspondances. Les redirections sont devenues incohérentes.

Parce qu’ils avaient l’habitude du snapshot, le réviseur a remarqué que HTTPS www redirigeait vers HTTP apex — clairement incorrect — avant que le changement n’atteigne la production. Pas d’héroïsme, pas de bridge incident, pas de « on fera un postmortem ». Juste un changement rejeté et une correction.

La pratique ne semblait pas innovante. Elle l’était. Le meilleur travail ops ressemble souvent à de la paperasserie jusqu’au jour où cela prévient un incendie.

Erreurs courantes (symptôme → cause racine → correction)

1) Symptom : HTTP ↔ HTTPS ping-pong

Cause racine : TLS termine sur un proxy, mais l’origine redirige en se basant sur $scheme ou un en-tête transféré non fiable. Ou il existe une redirection explicite HTTPS→HTTP laissée d’une ancienne migration.

Correction : Appliquez le schéma au point de terminaison TLS. Si Nginx doit l’appliquer derrière un proxy, utilisez $http_x_forwarded_proto mappé sur un $client_scheme, et faites-lui confiance seulement depuis les IPs proxy.

2) Symptom : www ↔ apex ping-pong

Cause racine : Nginx canonicalise vers www, l’application canonicalise vers l’apex (ou inversement). Parfois le CDN canonicalise d’un sens et l’origine de l’autre.

Correction : Choisissez un seul hôte canonique et appliquez-le dans une couche. Désactivez les redirections d’hôte de l’autre couche ou configurez son URL de base pour qu’elle corresponde.

3) Symptom : Seuls certains utilisateurs voient la boucle

Cause racine : 301 mis en cache dans les navigateurs, CDN ou proxies d’entreprise. Ou vous avez plusieurs origines/instances avec des configs différentes.

Correction : Purgez les caches CDN pour les réponses de redirection si applicable. Testez avec un client propre et curl. Vérifiez la cohérence des configs entre instances.

4) Symptom : La boucle n’apparaît que sur un chemin spécifique

Cause racine : La normalisation du slash final diffère entre Nginx et l’application pour cette route, ou try_files déclenche une redirection de répertoire que l’application rejette.

Correction : Définissez une politique de chemin unique. Soit laissez l’application gérer cela et évitez les redirections de chemin côté Nginx, soit implémentez des redirections explicites et cohérentes dans Nginx et désactivez la normalisation applicative.

5) Symptom : Les redirections pointent vers un nom d’hôte interne ou un mauvais port

Cause racine : Mauvaise utilisation de $host derrière un proxy qui réécrit host, ou l’application génère des URLs absolues basées sur l’adresse d’écoute interne. Parfois c’est proxy_redirect qui fait de la réécriture « utile ».

Correction : Utilisez un domaine canonique fixe dans return 301. Configurez l’amont pour connaître son URL publique. Auditez les réglages de proxy_redirect.

6) Symptom : Le navigateur boucle encore après que vous ayez corrigé la config

Cause racine : 301 mis en cache dans le navigateur, ou HSTS forçant HTTPS et exposant un autre problème de redirection, ou vous n’avez pas rechargé Nginx (cela arrive plus souvent qu’on l’admet).

Correction : Vérifiez avec curl depuis un environnement propre. Confirmez le reload et la config active avec nginx -T. Si HSTS est activé, assurez-vous que le point HTTPS est correct avant de toucher le comportement HTTP.

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

Étape par étape : corriger les boucles canonique + HTTPS en toute sécurité

  1. Écrivez votre politique canonique en une phrase : « Canonique est https://example.com (pas de www). » Si vous ne pouvez pas l’écrire, vous ne pouvez pas l’appliquer.
  2. Collectez les chaînes de redirection pour quatre points d’entrée : HTTP apex, HTTP www, HTTPS apex, HTTPS www. Enregistrez les codes d’état et les cibles Location.
  3. Contournez les intermédiaires (CDN/LB) pour voir si l’origine boucle elle-même.
  4. Rendez la config Nginx active avec nginx -T et cherchez toutes les directives de redirection.
  5. Éliminez les contradictions : supprimez toute règle qui redirige le canonique → non-canonique.
  6. Décidez où se situe l’enforcement du schéma : si TLS termine au LB/CDN, appliquez HTTPS là-bas, pas à l’origine — à moins d’implémenter la logique trusted forwarded-proto.
  7. Décidez où se situe l’enforcement d’hôte : faites-le à la périphérie (Nginx ou CDN), et désactivez les redirections d’hôte de l’application ou configurez l’URL de base en conséquence.
  8. Rechargez en toute sécurité (nginx -t puis systemctl reload), retestez avec curl, puis testez avec un navigateur.
  9. Supprimez les logs diagnostics temporaires après avoir confirmé la stabilité. Le diagnostic est excellent ; le bruit permanent ne l’est pas.
  10. Ajoutez un test de régression : une vérification scriptée qui confirme que ces quatre points d’entrée ont au plus une redirection et aboutissent à l’URL canonique.

Checklist opérationnelle : avant de déclarer victoire

  • L’URL canonique renvoie 200 (ou le statut attendu de l’application), pas 301.
  • Les URLs non canoniques renvoient exactement une redirection vers le canonique.
  • La cible de redirection ne rétrograde jamais HTTPS vers HTTP.
  • Aucune redirection ne pointe vers un nom d’hôte interne, une IP privée ou un port inattendu.
  • Les logs confirment les variables Host et scheme correctes.
  • Les checks de santé (LB/CDN) ne suivent pas des redirections qui cassent l’accessibilité de l’origine.

FAQ

1) Pourquoi le navigateur dit « Trop de redirections » alors que le journal d’erreurs Nginx est calme ?

Parce que les redirections ne sont pas des erreurs pour Nginx. Un 301 est une réponse normale. C’est le navigateur qui détecte la boucle après avoir suivi plusieurs redirections.

2) Dois-je utiliser rewrite ou return 301 dans Nginx ?

Utilisez return 301 pour les redirections d’hôte/schéma canoniques. C’est plus clair et moins susceptible de créer des boucles. Utilisez rewrite seulement quand vous avez réellement besoin de manipulations d’URI basées sur des regex.

3) Mon TLS se termine au load balancer. Est-ce que if ($scheme = http) est toujours faux ?

C’est faux pour décider de ce que le client a utilisé, car $scheme reflète le saut LB→origine. Utilisez un en-tête forwarded proto de confiance et mappez-le sur une variable comme $client_scheme.

4) Puis-je faire confiance à X-Forwarded-Proto depuis Internet ?

Non. N’importe quel client peut l’envoyer. Faites-y confiance uniquement depuis des plages IP de proxy connues. Sinon, vous laissez des attaquants influencer des comportements sensibles à la sécurité.

5) Pourquoi je vois un comportement différent entre curl et le navigateur ?

Les navigateurs mettent en cache les 301, peuvent appliquer HSTS, et parfois ont du DNS en cache ou des service workers. Curl est généralement « propre » sauf si vous scriptgez le cache. Si le navigateur diffère, testez en fenêtre privée et confirmez le statut HSTS.

6) Quel code d’état devrais-je utiliser pour HTTP → HTTPS ?

Pour les sites typiques, 301 convient. Si vous redirigez des POST et tenez à préserver la méthode, envisagez 308. La cohérence importe plus que la mode.

7) J’ai corrigé la boucle, mais maintenant je suis redirigé vers le mauvais nom d’hôte. Pourquoi ?

Probablement parce que vous avez utilisé $host dans la redirection et que l’en-tête Host entrant n’est pas ce que vous pensiez (réécritures proxy, domaines alternatifs). Pour la canonicalisation, préférez un domaine fixe dans le return.

8) Comment arrêter les boucles liées aux slashs finaux ?

Choisissez une politique et appliquez-la une seule fois. Si votre application veut « pas de slash final », désactivez les comportements implicites de Nginx qui l’ajoutent et configurez l’application pour générer des liens cohérents. Si Nginx en est responsable, rendez la redirection explicite et assurez-vous que l’application ne redirige pas en retour.

9) Debian 13 change-t-il quelque chose concernant les redirections Nginx ?

Pas fondamentalement. Les modes de défaillance sont les mêmes. Ce qui change en pratique, c’est que les mises à jour réordonnent souvent les fichiers inclus ou activent de nouveaux sites, augmentant le risque de blocs server dupliqués et de redirections conflictuelles.

Conclusion : prochaines étapes pour éviter les incidents récurrents

Les boucles de redirection ne sont pas mystérieuses. Ce sont des politiques contradictoires exécutées fidèlement. Votre travail consiste à supprimer la contradiction.

Prochaines étapes qui rapportent immédiatement :

  • Déclarez une URL canonique (schéma + hôte) et appliquez-la dans une seule couche.
  • Rendez Nginx conscient du proxy seulement quand c’est nécessaire, et seulement avec des en-têtes transférés de confiance.
  • Ajoutez un test de régression de chaîne de redirection aux déploiements : quatre points d’entrée en, un point d’arrivée canonique out.
  • Gardez les redirections ennuyeuses : utilisez return, évitez les réécritures sophistiquées, et supprimez les anciennes règles de migration une fois qu’elles ont servi.

Si vous faites cela correctement une fois, vous arrêterez de voir le cas n°71 apparaître lors des mises à jour, des changements CDN, ou de ce « petit réglage SEO » du vendredi après-midi qui atterrit toujours en production.

← Précédent
Permissions du répertoire web Debian/Ubuntu : arrêter les 403 sans 777 (Cas n°69)
Suivant →
WordPress « Le dossier de destination existe déjà » : réparer les installations sans compromettre wp-content

Laisser un commentaire