Debian 13 : Corriger « Trop de redirections » dans Nginx en supprimant la boucle canonique/HTTPS

Cet article vous a aidé ?

Vous déployez une machine Debian 13 propre, installez Nginx, ajoutez une redirection « simple » HTTP→HTTPS, et soudainement le navigateur vous affiche
« Trop de redirections ». Vous supprimez les cookies. Vous essayez le mode navigation privée. Vous accusez le navigateur. Puis Nginx.
Tout le monde est innocent sauf votre configuration.

Les boucles de redirection sont rarement « un bug de Nginx ». Elles résultent presque toujours d’un désaccord sur l’URL canonique
(hôte, schéma, ou les deux), amplifié par un reverse proxy ou un CDN qui ment — parfois poliment — sur le fait que la requête est HTTPS.
Corrigeons cela proprement, avec des preuves, pas des extraits copiés-collés.

Ce que signifie réellement « Trop de redirections »

Votre navigateur (ou client) a demandé une URL. Le serveur a répondu par une redirection (3xx) vers une autre URL. Le client l’a suivie.
Ce serveur a répondu par une autre redirection. Répétez jusqu’à ce que le client atteigne sa limite maximale de redirections et abandonne.

Dans l’univers Nginx, les boucles tendent à être :

  • Boucles de schéma : HTTP→HTTPS→HTTP→… parce que quelque chose en amont/aval n’est pas d’accord sur le schéma.
  • Boucles d’hôte : example.com→www.example.com→example.com→… parce que plusieurs couches « canoniques » diffèrent.
  • Mélanges port/schéma : rediriger vers https://host:80 ou http://host:443 accidentellement, souvent via des variables.
  • Redirections au niveau de l’application : l’application redirige vers HTTPS, Nginx redirige vers HTTP (ou un hôte différent), ils se battent.
  • Comportement « collant » induit par les cookies/HSTS : vos tests diffèrent parce que votre navigateur a mis en cache une politique ou un cookie.

La correction n’est pas « ajouter plus d’instructions if » jusqu’à ce que cela cesse. La correction consiste à définir exactement une autorité pour l’hôte canonique et le schéma,
puis faire en sorte que chaque autre couche s’y conforme. Votre pile devrait avoir une opinion, pas un comité.

Blague #1 : Les boucles de redirection sont comme des réunions sur des réunions — tout le monde continue de « faire avancer » et en fin de compte rien n’arrive.

Faits et historique des redirections utiles en production

Quelques points de contexte courts qui semblent académiques jusqu’à ce qu’ils vous mordent un mardi à 02:00 :

  1. Les redirections HTTP prédatent l’UX web « moderne ». Les codes 301/302 existaient dans les premières spécifications HTTP ; les caches et clients les traitent encore différemment aujourd’hui.
  2. Le 301 peut être mis en cache agressivement. Les navigateurs et intermédiaires peuvent garder en cache un 301 plus longtemps que prévu, donc « j’ai corrigé » peut ne pas se propager jusqu’à votre portable.
  3. Le 302 signifiait historiquement « temporaire », mais les clients ont été créatifs. Ce bazar explique l’existence de 307 et 308 : ils conservent la sémantique de la méthode de façon plus prévisible.
  4. HSTS change la donne. Une fois qu’un navigateur apprend « toujours utiliser HTTPS pour cet hôte », il peut ne jamais tenter HTTP à nouveau, compliquant le débogage.
  5. Les reverse proxies ont rendu « est-ce HTTPS ? » ambigu. Si TLS se termine en amont, votre Nginx voit du HTTP et vous devez compter sur des en-têtes ou le protocole PROXY.
  6. L’en-tête Host est à la fois puissant et dangereux. Il permet l’hébergement virtuel, mais lui faire confiance aveuglément peut créer des redirections ouvertes ou un chaos de canonisation.
  7. Les CDN ont introduit des modes « Flexible SSL ». Ces modes parlent HTTPS au navigateur mais HTTP à votre origine — terrain propice aux boucles HTTP→HTTPS.
  8. La sélection du serveur par défaut est un piège silencieux. Nginx choisira un « default_server » si rien ne correspond ; une redirection là peut détourner des hôtes non concernés.
  9. La canonicalisation n’est pas seulement du SEO. Elle affecte les cookies (portée de domaine), CORS, OAuth redirect URIs, et la persistance de session.

Une citation, parce qu’elle tient en production : L’espoir n’est pas une stratégie.Gordon R. Sullivan

Mode opératoire pour un diagnostic rapide (vérifiez ceci en premier)

Quand quelqu’un signale « site en panne, trop de redirections », ne commencez pas par regarder la config Nginx comme si elle vous devait de l’argent.
Commencez par répondre rapidement à trois questions et avec des preuves.

1) La boucle est-elle basée sur l’hôte, le schéma, ou l’application ?

  • Utilisez curl -IL pour voir la chaîne de redirection et constater ce que chaque saut modifie.
  • Si ça alterne entre http/https : problème de schéma (ou en-têtes proxy).
  • Si ça alterne entre www/non-www : conflit d’hôte canonique (Nginx vs application vs CDN).
  • Si l’URL reste la même mais que ça renvoie encore 301/302 : l’application peut se rediriger elle-même selon des en-têtes/cookies.

2) Où TLS est-il terminé ?

  • Si TLS se termine sur Nginx : utilisez $scheme et écoutez le 443 avec certificats.
  • Si TLS se termine sur un load balancer/CDN : Nginx voit du HTTP, donc les redirections doivent être basées sur X-Forwarded-Proto ou le PROXY protocol.
  • Si vous n’êtes pas sûr : vérifiez ss -lntp et la configuration de votre plateforme en amont.

3) Qui possède la canonicalisation ?

  • Choisissez exactement un propriétaire : Nginx edge, CDN, ou application.
  • Si à la fois Nginx et l’application redirigent vers le « canonique », vous finirez par créer une boucle à cause d’un petit décalage (port, hôte, slash final, schéma).

Faites ces trois vérifications et vous arrêterez de deviner. La plupart des boucles se réduisent à un mauvais « if ($scheme = http) » derrière un proxy, ou à des règles www conflictuelles.

Établir la vérité terrain : qui termine TLS et qui décide du canonique

Sur Debian 13, Nginx se comporte comme sur Debian 12, mais votre environnement probablement pas.
« Debian 13 » est souvent synonyme de « nouvelle VM, nouveaux paramètres par défaut, nouveau comportement LB, nouvelle automatisation de certificats, configuration copiée-collée ».

Décidez votre politique d’URL canonique (écrivez-la)

Choisissez un hôte canonique et un schéma canoniques. Exemples :

  • https://example.com (sans www) est canonique ; tout le http et www redirigent vers là.
  • https://www.example.com est canonique ; tout non-www redirige vers là, et tout http redirige vers https.

Ne faites pas « les deux fonctionnent » via des piles de redirections séparées. Faites-les arriver à un seul point.

Décidez où vivent les redirections

Mon avis tranché : effectuez les redirections canoniques à l’edge que vous contrôlez le plus fiablement. Pour beaucoup de déploiements Debian/Nginx,
c’est Nginx lui‑même. Pour des configurations CDN lourdes, le CDN peut le faire — si vous êtes discipliné et retirez la même logique de Nginx/application.

Si le framework applicatif force aussi HTTPS (commun dans Django, Rails, Laravel, Spring), vous avez le choix :

  • Désactivez cela dans l’application et laissez Nginx s’en occuper, ou
  • Laissez l’application gérer cela et assurez-vous que Nginx ne la contredit pas (pas de rewrites conflictuels, en-têtes forward corrects).

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

Vous réparerez cela plus vite en collectant des signaux. Ci‑dessous des tâches pratiques à exécuter sur Debian 13.
Chaque tâche inclut : commande, sortie d’exemple, ce que cela signifie, et la décision à prendre.

Task 1: Confirm Nginx is running and which config is loaded

cr0x@server:~$ systemctl status nginx --no-pager
● nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/lib/systemd/system/nginx.service; enabled; preset: enabled)
     Active: active (running) since Mon 2025-12-30 09:10:18 UTC; 2h 3min ago
       Docs: man:nginx(8)
   Main PID: 1642 (nginx)
      Tasks: 3 (limit: 18988)
     Memory: 7.4M
        CPU: 1.231s
     CGroup: /system.slice/nginx.service
             ├─1642 "nginx: master process /usr/sbin/nginx -g daemon on; master_process on;"
             ├─1643 "nginx: worker process"
             └─1644 "nginx: worker process"

Signification : Nginx est actif. Vous ne déboguez pas un service mort.
Décision : Passez à l’inspection de la config ; si le service n’est pas actif, corrigez d’abord les erreurs de démarrage.

Task 2: Validate config syntax (don’t skip this)

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

Signification : La syntaxe est correcte. Les boucles de redirection sont logiques, pas d’erreurs à la compilation.
Décision : Passez à la « configuration effective » et à la chaîne de redirection.

Task 3: Dump the effective config and find the redirect rules actually applied

cr0x@server:~$ sudo nginx -T 2>/dev/null | sed -n '1,140p'
# configuration file /etc/nginx/nginx.conf:
user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events { worker_connections 768; }
http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

Signification : Nginx inclut à la fois conf.d et sites-enabled.
Décision : Recherchez dans le dump complet return 301, rewrite, $scheme, et X-Forwarded-Proto.

Task 4: Locate all redirect statements and the server blocks they live in

cr0x@server:~$ sudo nginx -T 2>/dev/null | egrep -n 'server_name|listen|return 30|rewrite|X-Forwarded-Proto|proxy_redirect'
58:    server {
59:        listen 80 default_server;
60:        server_name _;
61:        return 301 https://$host$request_uri;
84:    server {
85:        listen 443 ssl http2;
86:        server_name example.com www.example.com;
101:        if ($host = www.example.com) { return 301 https://example.com$request_uri; }
129:        proxy_set_header X-Forwarded-Proto $scheme;

Signification : Il y a une redirection par défaut sur le port 80 et une redirection de host canonique sur 443.
Décision : Cela peut encore être correct — mais si vous êtes derrière un proxy qui parle HTTP à Nginx, $scheme vaudra http et peut déclencher des boucles.

Task 5: Observe the redirect chain from the server itself (bypass your browser)

cr0x@server:~$ curl -IL http://example.com/
HTTP/1.1 301 Moved Permanently
Server: nginx
Location: https://example.com/

HTTP/2 301
server: nginx
location: https://example.com/

HTTP/2 301
server: nginx
location: https://example.com/

Signification : Même cible répétée : https://example.com/ redirige vers elle‑même. Ce n’est pas « HTTP→HTTPS ». C’est une condition logique qui se déclenche sur des requêtes HTTPS.
Décision : Inspectez le bloc serveur 443 pour une redirection inconditionnelle (souvent via un if erroné, une variable, ou un comportement d’upstream applicatif).

Task 6: Check whether 443 is actually terminating TLS on this host

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

Signification : Nginx écoute sur les deux ports localement. Cela suggère que la terminaison TLS est ici (à moins qu’un proxy ne tunnelise différemment).
Décision : Si vous êtes derrière un load balancer, confirmez s’il se connecte à l’origine sur 443 ou 80.

Task 7: Verify what certificate Nginx is serving (confirms you’re hitting the right box)

cr0x@server:~$ echo | openssl s_client -connect 127.0.0.1:443 -servername example.com 2>/dev/null | openssl x509 -noout -subject -issuer -dates
subject=CN = example.com
issuer=C = US, O = Let's Encrypt, CN = R11
notBefore=Dec  1 00:12:33 2025 GMT
notAfter=Mar  1 00:12:32 2026 GMT

Signification : La terminaison TLS se fait effectivement sur Nginx et le SNI correspond.
Décision : Concentrez‑vous sur la logique 443 de Nginx et sur les redirections d’upstream applicatif plutôt que sur une terminaison TLS externe.

Task 8: Inspect the access log for repeated 301/302 patterns

cr0x@server:~$ sudo tail -n 8 /var/log/nginx/access.log
203.0.113.44 - - [30/Dec/2025:11:18:19 +0000] "GET / HTTP/2.0" 301 169 "-" "curl/8.10.1"
203.0.113.44 - - [30/Dec/2025:11:18:19 +0000] "GET / HTTP/2.0" 301 169 "-" "curl/8.10.1"
203.0.113.44 - - [30/Dec/2025:11:18:20 +0000] "GET / HTTP/2.0" 301 169 "-" "curl/8.10.1"

Signification : Le même client est redirigé à répétition sur le même chemin.
Décision : Il s’agit probablement d’une redirection inconditionnelle dans le serveur 443, ou d’une application qui redirige et que Nginx redirige à son tour.

Task 9: Turn on a dedicated redirect debug log format temporarily

cr0x@server:~$ sudo install -m 0644 /dev/null /var/log/nginx/redirect-debug.log
cr0x@server:~$ sudo tee /etc/nginx/conf.d/redirect-debug.conf >/dev/null <<'EOF'
log_format redir '$remote_addr "$host" $server_name $scheme '
                'xfp="$http_x_forwarded_proto" xfh="$http_x_forwarded_host" '
                '"$request" => $status loc="$sent_http_location" '
                'ua="$http_user_agent"';
access_log /var/log/nginx/redirect-debug.log redir;
EOF
cr0x@server:~$ sudo nginx -t && sudo systemctl reload nginx
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Signification : Vous consignez maintenant le schéma et les en‑têtes forward pour chaque requête.
Décision : Reproduisez le problème une fois, puis lisez le log de debug pour voir quel signal a déclenché la redirection.

Task 10: Reproduce and read the redirect debug log

cr0x@server:~$ curl -IL https://example.com/ -H 'Host: example.com'
HTTP/2 301
server: nginx
location: https://example.com/
cr0x@server:~$ sudo tail -n 3 /var/log/nginx/redirect-debug.log
203.0.113.44 "example.com" example.com https xfp="-" xfh="-" "GET / HTTP/2.0" => 301 loc="https://example.com/" ua="curl/8.10.1"

Signification : Nginx croit que le schéma est https, et pourtant a renvoyé un 301 vers la même URL.
Décision : Cette redirection n’est pas conditionnelle au schéma ; c’est une mauvaise règle de canonicalisation ou un rewrite/return placé dans le mauvais serveur/location.

Task 11: List enabled sites and confirm you don’t have dueling server blocks

cr0x@server:~$ ls -l /etc/nginx/sites-enabled/
total 0
lrwxrwxrwx 1 root root 34 Dec 30 09:05 default -> /etc/nginx/sites-available/default
lrwxrwxrwx 1 root root 39 Dec 30 09:07 example -> /etc/nginx/sites-available/example

Signification : Le site par défaut est encore activé. Sur les nouvelles installations, il contient souvent une redirection ou un catch‑all.
Décision : Si votre bloc serveur « default » fait des redirections, il peut intercepter des hostnames inattendus. Désactivez‑le sauf si vous l’utilisez activement.

Task 12: Find which server block answers for a hostname (default_server traps)

cr0x@server:~$ sudo nginx -T 2>/dev/null | awk '
/server \{/ {in=1; blk=""; next}
in {blk=blk $0 "\n"}
/\}/ && in {print "----\n" blk; in=0}' | egrep -n 'listen 80|listen 443|server_name'
3:        listen 80 default_server;
4:        server_name _;
11:        listen 80;
12:        server_name example.com www.example.com;
19:        listen 443 ssl http2;
20:        server_name example.com www.example.com;

Signification : Il y a un default_server explicite sur le port 80 qui répondra à tout. C’est acceptable si il renvoie 444/404 ; dangereux s’il redirige.
Décision : Faites en sorte que votre serveur par défaut retourne 444 ou un 404 minimal — pas une redirection — sauf si vous contrôlez tous les Hostnames entrants.

Task 13: Check whether the application upstream is doing the redirect

cr0x@server:~$ sudo grep -R "proxy_pass" -n /etc/nginx/sites-available/example
42:        proxy_pass http://127.0.0.1:8080;
cr0x@server:~$ curl -I http://127.0.0.1:8080/
HTTP/1.1 301 Moved Permanently
Location: https://example.com/
Server: gunicorn

Signification : L’application redirige elle‑même vers HTTPS. Si Nginx redirige aussi, vous pouvez facilement boucler (surtout derrière un proxy).
Décision : Laissez soit l’application gérer les redirections HTTPS et configurez correctement les en‑têtes forward, soit désactivez la redirection au niveau applicatif et laissez Nginx s’en occuper.

Task 14: Check for HSTS that makes your browser “fight” your tests

cr0x@server:~$ curl -I https://example.com/ | egrep -i 'strict-transport-security|location|http/'
HTTP/2 301
location: https://example.com/
strict-transport-security: max-age=31536000; includeSubDomains

Signification : Si HSTS est présent, les navigateurs forceront HTTPS pour cet hôte pendant la durée de max-age.
Décision : Pendant un incident de redirection, testez avec curl et des hôtes propres ; ne vous fiez pas à votre folklore « ça marche en privé ».

Task 15: Watch Nginx errors during reloads and request handling

cr0x@server:~$ sudo journalctl -u nginx -n 50 --no-pager
Dec 30 11:14:02 server nginx[1642]: 2025/12/30 11:14:02 [notice] 1642#1642: signal process started
Dec 30 11:18:20 server nginx[1644]: 2025/12/30 11:18:20 [info] 1644#1644: *912 client 203.0.113.44 closed keepalive connection

Signification : Pas d’échec de reload. Les logs ne diront pas directement « boucle de redirection » mais montreront les motifs de requêtes et problèmes de reload.
Décision : Si les reloads échouent, corrigez d’abord la syntaxe/permissions. Sinon continuez avec les access logs et traces curl.

La bonne correction : un seul chemin de redirection canonique

Le schéma fiable est ennuyeux. C’est pour ça qu’il fonctionne.

  • Ayez un bloc serveur pour HTTP qui ne fait que rediriger vers l’hôte HTTPS canonique.
  • Ayez un bloc serveur pour l’hôte HTTPS canonique qui sert le contenu (ou proxie).
  • Éventuellement, ayez un bloc HTTPS pour l’hôte non canonique qui redirige vers le canonique.
  • Ne redirigez jamais des requêtes HTTPS vers la même URL HTTPS (oui, des gens le font par accident avec des variables).
  • Derrière un reverse proxy, n’utilisez pas $scheme sauf si TLS se termine sur Nginx.

Pourquoi les boucles arrivent : collision entre hôte canonique et zèle HTTPS

Forme de boucle commune :

  1. Le CDN termine TLS, envoie du HTTP à l’origine.
  2. L’origine Nginx dit « si le schéma est http, redirigez vers https ». Il voit $scheme=http et redirige.
  3. Le client suit vers HTTPS au CDN à nouveau. Le CDN répète HTTP à l’origine. L’origine redirige encore. Boucle.

Autre forme de boucle :

  1. Nginx sur 443 dit « si l’hôte est www, redirigez vers non‑www ».
  2. L’application dit « si l’hôte est non‑www, redirigez vers www » (quelqu’un a mal configuré une variable d’environnement).
  3. Boucle. Chacun « a raison » dans sa propre vision du monde.

Blague #2 : Les redirections Nginx sont comme la politique de bureau — une fois que deux départements se partagent la même décision, vous tournerez en rond.

Conseil fort : choisissez un propriétaire, puis supprimez les autres

Si Nginx est votre propriétaire canonique :

  • Désactivez les réglages du framework qui forcent HTTPS, ou configurez‑les pour qu’ils fassent confiance aux en‑têtes proxy et n’effectuent pas de re‑redirections.
  • Faites en sorte que Nginx calcule l’hôte canonique une fois et l’applique de manière cohérente.

Si l’application est votre propriétaire canonique :

  • Ne faites pas de redirections d’hôte/schéma dans Nginx. Utilisez Nginx comme tuyau simple avec des en‑têtes X-Forwarded-* corrects.
  • Assurez‑vous que l’application ne fait confiance à ces en‑têtes qu’aux adresses IP de votre proxy (la sécurité compte ; les redirections ouvertes et le spoofing existent).

Configs Nginx de référence qui n’entraînent pas de boucle

Ces modèles sont volontairement simples. Les configs sophistiquées ne gagnent pas de prix de disponibilité.
Remplacez example.com et les détails d’upstream selon vos besoins.

Case A: TLS terminates at Nginx (most VPS deployments)

Canonique : https://example.com. Rediriger tout le reste vers cela.

cr0x@server:~$ sudo tee /etc/nginx/sites-available/example >/dev/null <<'EOF'
# Canonical: https://example.com

# 1) HTTP: redirect to canonical HTTPS host
server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

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

# 2) HTTPS for non-canonical host: redirect to canonical
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;
}

# 3) HTTPS canonical: serve
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;

    # Optional: keep this minimal during incidents
    access_log /var/log/nginx/example.access.log;
    error_log  /var/log/nginx/example.error.log;

    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;
    }
}
EOF
cr0x@server:~$ sudo ln -sf /etc/nginx/sites-available/example /etc/nginx/sites-enabled/example
cr0x@server:~$ sudo nginx -t && sudo systemctl reload nginx
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Pourquoi cela fonctionne : pas d’« if ($scheme …) » dans le serveur 443 canonique. Les requêtes HTTPS ne seront pas re‑redirigées sur la base du schéma.
Les redirections d’hôte n’arrivent que pour l’hôte non‑canonique.

Case B: TLS terminates upstream (load balancer/CDN), origin is HTTP only

Si le load balancer se connecte à Nginx sur le port 80, votre origine ne peut pas s’appuyer sur $scheme. Il vaudra toujours http.
Dans ce monde, vous devriez typiquement éviter les redirections HTTP→HTTPS à l’origine et les faire à l’edge.

Mais parfois vous devez appliquer le canonique à l’origine (conformité, multiples edges, DNS corporate désordonné). Dans ce cas :

  • Faites confiance à X-Forwarded-Proto seulement depuis les plages IP du LB/CDN.
  • Utilisez un map pour calculer le « vrai schéma » et ne rediriger que lorsqu’il s’agit réellement de HTTP côté client.
cr0x@server:~$ sudo tee /etc/nginx/conf.d/real-scheme.conf >/dev/null <<'EOF'
# Compute client-facing scheme safely-ish (still requires IP restrictions below).
map $http_x_forwarded_proto $client_scheme {
    default $scheme;
    "~*^https$" https;
    "~*^http$"  http;
}
EOF
cr0x@server:~$ sudo tee /etc/nginx/sites-available/example >/dev/null <<'EOF'
# Origin assumes it may be behind a trusted proxy providing X-Forwarded-Proto.
# Canonical: https://example.com

# IMPORTANT: Restrict who can send spoofed X-Forwarded-Proto
# Replace these with your LB/CDN addresses.
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;

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

    # Redirect only if the *client-facing* scheme is http
    if ($client_scheme = http) {
        return 301 https://example.com$request_uri;
    }

    # If it's already HTTPS at the edge, serve/proxy normally.
    location / {
        proxy_pass http://127.0.0.1:8080;

        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Proto $client_scheme;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}
EOF
cr0x@server:~$ sudo nginx -t && sudo systemctl reload nginx
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Avertissement : Cet « if » est acceptable car il s’agit d’un simple return au niveau serveur. Préférez quand même les redirections au niveau edge quand possible.
Aussi : si vous ne restreignez pas qui peut définir les en‑têtes forward, un client peut les falsifier et causer des problèmes de sécurité.

Case C: Default server should not redirect (reduce blast radius)

Votre serveur par défaut existe pour gérer en toute sécurité des Host headers invalides. Il ne devrait pas « canoniquement » renvoyer vers votre vrai domaine.
C’est comme ça que vous finissez par servir le domaine de quelqu’un d’autre via votre site et créer des tempêtes de redirection étranges.

cr0x@server:~$ sudo tee /etc/nginx/sites-available/default >/dev/null <<'EOF'
server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;

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

Signification : Nginx ferme la connexion sans réponse. C’est correct pour les hôtes inconnus.
Décision : Gardez‑le. Cela évite un comportement de redirection accidentel et fait en sorte que vos règles canoniques s’appliquent seulement où prévu.

Derrière un load balancer/CDN : ne plus faire confiance au mauvais signal

La plupart des boucles de redirection en 2025 ne sont pas « Nginx ne sait pas rediriger ». Ce sont « Nginx se voit raconter la mauvaise histoire sur la requête ».
Si un proxy termine TLS, votre origine voit du HTTP. Si votre origine insiste pour rediriger HTTP→HTTPS, vous avez une roue de hamster.

Reconnaître la signature classique d’une boucle proxy

Dans curl -IL, vous voyez :

  • Location: https://example.com/… répété
  • Status 301/302 encore et encore
  • Et vos logs d’origine montrent uniquement des requêtes HTTP, jamais le 443

Arbre de décision : que faire

  1. Si vous le pouvez : gérez la redirection HTTP→HTTPS à l’edge (LB/CDN). Désactivez‑la à l’origine. C’est la solution la plus propre.
  2. Si vous devez la faire à l’origine : faites confiance à un en‑tête de schéma forward du proxy, et seulement depuis le proxy.
  3. Si vous ne pouvez pas faire confiance aux en‑têtes : utilisez le PROXY protocol de bout en bout (LB → Nginx) et configurez Nginx en conséquence.

Vérifiez les en‑têtes forward arrivant à Nginx

Votre log de debug de redirection de la Tâche 9 affiche déjà :
xfp="$http_x_forwarded_proto" et xfh="$http_x_forwarded_host".
Si ces valeurs sont vides, votre edge ne les envoie pas, ou Nginx ne reçoit pas la requête que vous croyez recevoir.

Pourquoi « mettre proxy_set_header X-Forwarded-Proto $scheme » peut être faux

Cette ligne est souvent copiée dans la config du proxy d’origine. Elle définit X-Forwarded-Proto sur le schéma de l’origine, pas sur celui du client.
Si TLS se termine en amont, $scheme vaut http. Félicitations, vous venez de dire à votre appli « c’est HTTP » pour toujours.

Si votre application applique HTTPS basé sur X-Forwarded-Proto, elle redirigera constamment. Si Nginx applique HTTPS basé sur $scheme,
il redirigera aussi constamment. La boucle n’est pas mystérieuse. Elle est obéissante.

Note sécurité : les en‑têtes forward sont des entrées utilisateur

Un client peut envoyer X-Forwarded-Proto: https directement à votre origine s’il peut l’atteindre.
Si vous faites confiance à cet en‑tête depuis l’internet ouvert, vous pouvez casser des hypothèses de sécurité et créer des comportements étranges de redirection et de cookie.
Restreignez par réseau, ou assurez‑vous que l’origine n’est pas directement exposée.

Trois mini‑histoires d’entreprise (réalistes, douloureuses, utiles)

Mini‑histoire 1 : L’incident causé par une mauvaise hypothèse

Une entreprise de taille moyenne a déplacé un portail client d’une paire Nginx on‑prem vers un load balancer géré devant une nouvelle flotte Debian.
Le plan de migration contenait une phrase qui les a condamnés : « TLS reste le même. » Tout le monde a hoché la tête parce que ça semblait rassurant.

On‑prem, Nginx terminait TLS. Dans la nouvelle configuration, le load balancer terminait TLS et parlait HTTP à l’origine parce que c’était « plus simple »
et l’équipe voulait éviter la distribution de certificats sur les instances. L’origine Nginx a conservé l’ancienne règle : return 301 https://$host$request_uri;
dans le serveur sur le port 80.

Le premier symptôme n’a pas été une alerte des monitors — c’était une explosion de tickets support : « La page de connexion ne cesse de se rafraîchir. »
Certains utilisateurs pouvaient charger la page d’accueil (mise en cache). D’autres ne pouvaient pas s’authentifier parce que la boucle de redirections empêchait l’établissement du cookie de session.
Les SRE ont vu des 301 dans les logs mais ont supposé « les redirections sont normales ; les navigateurs gèrent ça. »

La correction a été simple et légèrement humiliante : supprimer la redirection HTTP→HTTPS à l’origine, l’implémenter sur le load balancer,
et n’avoir la redirection canonique que sur une seule couche. La meilleure phrase du post‑mortem a été la plus ennuyeuse :
« Nous avons supposé que $scheme reflétait le schéma client. » Ce n’était pas le cas. Ce n’a jamais été le cas. Il reflétait le saut vers Nginx.

Mini‑histoire 2 : L’optimisation qui s’est retour­née contre eux

Une autre équipe, industrie différente. L’équipe a décidé d’« optimiser » en consolidant des blocs serveur.
Un méga bloc serveur gérerait HTTP et HTTPS, www et non‑www, plusieurs environnements,
et calculerait la cible de redirection en utilisant des variables pour garder la config « DRY ».

Ça a commencé innocemment : un map pour l’hôte canonique, une variable pour le schéma, et quelques if.
Puis est venu un flag de fonctionnalité pour le « mode maintenance » et un rewrite pour des anciens chemins.
Quelqu’un a ajouté return 301 $canonical_scheme://$canonical_host$request_uri; à l’intérieur d’un location qui correspondait trop largement.

En staging, cela « fonctionnait » parce que staging utilisait un seul hôte et pas de CDN. En production, derrière un proxy,
la variable de schéma canonique évaluait à https basée sur X-Forwarded-Proto — sauf qu’un chemin n’avait pas l’en‑tête
parce qu’une règle WAF l’avait supprimé pour certains user agents.

Résultat : une boucle de redirection qui affectait seulement un sous‑ensemble de clients et seulement sur la page de checkout. Le tableau de bord était vert.
Les revenus l’étaient moins. L’optimisation a économisé peut‑être 40 lignes de config et coûté des jours d’intervention.
Ils sont revenus à des blocs serveur séparés et ont accepté la duplication comme caractéristique de fiabilité.

Mini‑histoire 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise

Une équipe de services financiers avait une règle : tout changement Nginx touchant les redirections doit inclure une chaîne curl -IL enregistrée
pour quatre URLs : http/non‑www, http/www, https/non‑www, https/www. La sortie était collée dans la demande de changement.
Les gens râlaient. C’était bureaucratique.

Pendant une fenêtre de renouvellement de certificats, un nouvel ingénieur a réactivé accidentellement le site par défaut.
Le serveur par défaut sur le port 80 redirigeait vers l’hôte canonique. Pendant ce temps, l’application appliquait un autre hôte canonique
basé sur sa configuration. Cette combinaison a créé une boucle seulement pour les requêtes atteignant le serveur par défaut (i.e., tout Host non reconnu).

Le réviseur de la demande de changement l’a repéré parce que les chaînes curl requises montraient soudainement une redirection depuis un hostname inattendu en production.
Le réviseur a posé la question simple : « Pourquoi le default_server redirige‑t‑il vers n’importe où ? » Ils ont corrigé en faisant renvoyer 444 par le serveur par défaut.

Pas d’incident. Pas de rollback nocturne. Juste un processus ennuyeux qui a fait son travail. C’est le type d’ennuyeux que vous devriez apprendre à aimer.

Erreurs fréquentes : symptôme → cause racine → correction

1) Symptom: HTTP→HTTPS loop only when behind CDN/LB

Cause racine : TLS se termine au CDN/LB, l’origine voit HTTP, l’origine redirige vers HTTPS, le CDN répète.

Correction : Déplacez la redirection à l’edge, ou faites confiance à X-Forwarded-Proto depuis le proxy et basez‑vous dessus.

2) Symptom: https://example.com redirects to itself (same URL)

Cause racine : return 301 https://example.com$request_uri; inconditionnel à l’intérieur du serveur 443 (ou emplacement mal ciblé).

Correction : Supprimez la redirection du serveur HTTPS canonique. Redirigez uniquement depuis le serveur HTTP ou depuis le serveur de l’hôte non‑canonique.

3) Symptom: Alternating between www and non-www

Cause racine : Nginx et l’application ne sont pas d’accord sur l’hôte canonique (ou le CDN a sa propre redirection).

Correction : Choisissez un propriétaire pour la canonicalisation d’hôte. Désactivez les autres règles. Vérifiez avec curl -IL depuis plusieurs points.

4) Symptom: Works for some users, loops for others

Cause racine : Comportement éclaté via plusieurs POP edge, routage A/B, WAF qui supprime des en‑têtes, ou certains chemins déclenchant la logique applicative.

Correction : Ajoutez de la journalisation pour $scheme, $host, $sent_http_location, et les en‑têtes forward. Comparez requêtes échouées vs réussies.

5) Symptom: Only your browser fails; curl works

Cause racine : 301 mis en cache ou HSTS dans le navigateur, ou cookies obsolètes qui déclenchent des flux de redirection applicatifs.

Correction : Utilisez curl avec -IL. Supprimez HSTS pour l’hôte ou testez depuis un profil/appareil propre. Envisagez de retirer temporairement HSTS jusqu’à stabilité.

6) Symptom: Redirect loop started after enabling “default” site

Cause racine : default_server intercepte des requêtes et applique une redirection prévue pour un hôte spécifique.

Correction : Faites en sorte que le serveur par défaut retourne 444/404. Assurez‑vous que les vrais vhosts ont des server_name exacts et ne se concurrencent pas.

7) Symptom: OAuth login breaks with redirect_uri mismatch

Cause racine : L’hôte/schéma canonique change en cours de flux ; les redirections réécrivent schéma/hôte et le provider rejette.

Correction : Appliquez la canonicalisation avant les endpoints d’auth, et assurez‑vous que l’application voit le bon schéma/hôte via les en‑têtes forward.

8) Symptom: Websocket endpoints fail while normal pages work

Cause racine : Une redirection ou une enforcement de schéma appliquée aux chemins /ws ; l’upstream attend un upgrade et reçoit un 301.

Correction : Exemptez les emplacements websocket des redirections ; assurez‑vous que la canonicalisation se produit au niveau serveur, pas à l’intérieur des locations websocket.

Listes de contrôle / plan pas‑à‑pas

Plan pas‑à‑pas pour corriger la boucle sans deviner

  1. Capturez la chaîne de redirection.
    Exécutez curl depuis un environnement propre :

    cr0x@server:~$ curl -IL http://example.com/
    HTTP/1.1 301 Moved Permanently
    Location: https://example.com/
    
    HTTP/2 200
    

    Décision : Si la chaîne alterne schéma/hôte, vous savez contre quoi vous vous battez.

  2. Décidez l’hôte + le schéma canoniques par écrit. Exemple : « canonique : https://example.com ».

    Décision : Tout le reste redirige vers cela, une seule fois.

  3. Déterminez la terminaison TLS.

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

    Décision : Si Nginx n’écoute pas sur 443, arrêtez d’écrire des redirections basées sur $scheme à l’origine.

  4. Supprimez les propriétaires de redirection dupliqués. Vérifiez la config applicative pour « force SSL » / « canonical host » et désactivez ou alignez.

    Décision : Un seul propriétaire. Pas deux.

  5. Rendez le serveur par défaut inoffensif. Retourner 444 ou un 404 minimal.

    Décision : Réduisez le rayon d’impact des Host headers mal routés.

  6. Utilisez des blocs serveur séparés pour les redirections. Bloc de redirection HTTP ; bloc HTTPS canonique pour servir ; bloc HTTPS non‑canonique pour rediriger.

    Décision : Préférez la clarté à l’ingéniosité.

  7. Ajoutez une journalisation temporaire de debug des redirections si incertain. Capturez schéma/hôte/Location dans les logs.

    Décision : Ne discutez pas avec des opinions ; discutez avec des lignes de log.

  8. Reload et retestez avec curl. Validez toujours la config et rechargez proprement.

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

    Décision : Si la boucle persiste, c’est en amont (CDN/appli), pas une syntaxe Nginx.

Checklist opérationnelle (gardez‑la pour le futur vous)

  • Pour tout changement de redirection, enregistrez les chaînes curl pour 4 points d’entrée : http/https × www/non‑www.
  • Confirmez qui termine TLS et où les redirections sont appliquées.
  • Assurez‑vous que le default_server ne redirige pas vers un domaine réel.
  • Derrière des proxies : vérifiez la présence de X-Forwarded-Proto et les frontières de confiance.
  • Ne déployez pas à la fois le « force HTTPS » du CDN et celui de l’origine sauf si vous comprenez parfaitement le comportement des sauts.
  • Faites preuve de prudence avec HSTS ; ne l’activez qu’après vérification complète des redirections.

FAQ

1) Why does my browser say “Too many redirects” but curl sometimes works?

Les navigateurs mettent en cache les 301 et appliquent HSTS. Curl ne met généralement pas en cache entre les exécutions.
Si vous avez servi HSTS auparavant, votre navigateur peut forcer HTTPS et ne jamais atteindre la logique de redirection HTTP comme vous l’attendez.

2) Should I use 301 or 302 for canonical redirects?

Pour une canonicalisation stable (www→non‑www, http→https), utilisez 301 ou 308. Si vous expérimentez activement, utilisez 302/307 pour éviter un caching trop collant.
En production, soyez délibéré : un 301 peut rester en cache plus longtemps que votre patience.

3) Is it okay to use “if” in Nginx for redirects?

Un simple if qui fait immédiatement un return au niveau serveur est généralement acceptable.
La mauvaise réputation vient des réécritures complexes et de la logique imbriquée. Préférez des blocs serveur séparés quand c’est possible.

4) What’s the fastest way to see the redirect chain?

Utilisez curl -IL. Ajoutez --max-redirs 20 si nécessaire. Vous cherchez ce que chaque saut modifie : schéma, hôte, ou chemin.

5) I’m behind a CDN in “Flexible SSL”. Why does that cause loops?

Le Flexible SSL signifie souvent : navigateur↔CDN en HTTPS, CDN↔origine en HTTP.
Votre origine voit HTTP et redirige vers HTTPS. Le CDN continue d’utiliser HTTP vers l’origine. Boucle.
Corrigez en utilisant TLS complet vers l’origine ou en déplaçant les redirections sur le CDN et en les désactivant à l’origine.

6) Can the application framework cause the loop even if Nginx config looks right?

Absolument. Beaucoup de frameworks redirigent en fonction du schéma/hôte perçu.
Si l’application voit X-Forwarded-Proto: http (ou aucun en‑tête), elle peut rediriger vers HTTPS. Si Nginx redirige aussi, ou si l’hôte diffère, vous bouclez.

7) Why is leaving the default site enabled risky?

Parce que default_server attrape les hôtes non correspondants. S’il redirige vers votre domaine canonique,
vous pouvez accidentellement servir ou rediriger du trafic pour des Host headers inattendus, y compris des fautes de frappe et des sondes hostiles.
Faites retourner 444/404 par le serveur par défaut.

8) How do I know whether the CDN/LB is sending X-Forwarded-Proto?

Loggez‑le dans Nginx ($http_x_forwarded_proto) et vérifiez. S’il est vide, soit il n’est pas envoyé, soit il est supprimé, soit vous n’atteignez pas le chemin attendu.
Ne supposez pas ; vérifiez dans les logs.

9) Should I enable HSTS while fixing redirects?

Non. Corrigez d’abord les redirections. Ensuite activez HSTS une fois que vous êtes sûr que HTTPS est servi de façon cohérente sur l’hôte canonique.
HSTS est un dispositif d’engagement — excellent quand vous avez raison, pénible quand vous avez tort.

10) I fixed Nginx but users still report loops. What now?

Vérifiez l’edge (règles CDN/LB), les paramètres de cache, et si plusieurs origines existent.
Ensuite vérifiez les redirections côté application. Enfin, considérez la mise en cache navigateur/HSTS. La boucle peut être hors de la boîte que vous venez d’éditer.

Conclusion : prochaines étapes qui réduisent réellement le temps d’incident

Les boucles de redirection sont simples en théorie et pénibles en pratique parce que plusieurs couches se sentent autorisées à « corriger » l’URL.
Votre travail est de faire d’une seule couche l’adulte dans la pièce et de décharger les autres de cette responsabilité.

Prochaines étapes pratiques :

  1. Exécutez curl -IL pour http/https × www/non‑www et sauvegardez la chaîne dans vos notes d’incident.
  2. Décidez l’URL canonique (hôte + schéma) et choisissez un seul propriétaire des redirections.
  3. Si vous êtes derrière un proxy/CDN, cessez d’utiliser $scheme pour représenter le « schéma client » sauf si TLS se termine sur Nginx.
  4. Faites en sorte que le default_server retourne 444/404, pas des redirections.
  5. Rechargez Nginx en toute sécurité (nginx -t puis systemctl reload) et re‑testez la chaîne jusqu’à ce qu’elle converge en 1–2 sauts.
  6. Seulement après stabilité : envisagez d’activer HSTS, avec intention et plan de rollback.

Une fois que vous avez fait cela, « Trop de redirections » redevient ce qu’il devrait être : une correction rapide, pas un trait de personnalité.

← Précédent
Turbo Boost : comment les CPU trichent sur leurs fiches techniques (légalement)
Suivant →
L’ère du bouton reset : la réparation la plus honnête en informatique

Laisser un commentaire