Debian 13: Corregir “Demasiadas redirecciones” en Nginx eliminando el bucle canónico/HTTPS

¿Te fue útil?

Despliegas una máquina limpia con Debian 13, instalas Nginx, añades una redirección “simple” HTTP→HTTPS, y de repente el navegador te muestra
“Demasiadas redirecciones”. Borras las cookies. Pruebas en incógnito. Culpas al navegador. Luego culpas a Nginx.
Todos son inocentes excepto tu configuración.

Los bucles de redirección rara vez son “un bug de Nginx”. Casi siempre son un desacuerdo sobre cuál debe ser la URL canónica
(host, esquema o ambos), amplificado por un proxy inverso o CDN que miente—a veces con buenas maneras—sobre si la petición era HTTPS.
Arreglémoslo correctamente, con evidencias, no con fragmentos copiados sin entender.

Qué significa realmente “Demasiadas redirecciones”

Tu navegador (o cliente) solicitó una URL. El servidor respondió con una redirección (3xx) a otra URL. El cliente la siguió.
Ese servidor respondió con otra redirección. Repetir hasta que el cliente alcanza su límite máximo de redirecciones y se rinde.

En Nginx, los bucles suelen ser:

  • Bucle de esquema: HTTP→HTTPS→HTTP→… porque algo upstream/downstream no coincide sobre el esquema.
  • Bucle de host: example.com→www.example.com→example.com→… porque varias capas “canonicen” de forma distinta.
  • Confusiones puerto/esquema: redirigir accidentalmente a https://host:80 o http://host:443, a menudo por variables.
  • Redirecciones a nivel de la aplicación: la app redirige a HTTPS, Nginx redirige a HTTP (o a otro host), se enfrentan.
  • Comportamiento “pegajoso” por cookies/HSTS: tus pruebas difieren porque el navegador almacenó una política o cookie.

La solución no es “añadir más ifs” hasta que pare. La solución es definir exactamente una autoridad para host y esquema canónicos,
y hacer que todas las demás capas la sigan. Tu stack debe tener una sola opinión, no un comité.

Broma #1: Los bucles de redirección son como reuniones sobre reuniones—todos siguen “avanzando” y de alguna forma nunca se llega a nada.

Hechos y antecedentes sobre redirecciones relevantes en producción

Algunos puntos breves que parecen académicos hasta que te golpean un martes a las 02:00:

  1. Las redirecciones HTTP preceden a la UX web moderna. Los códigos 301/302 estaban en las primeras especificaciones HTTP; caches y clientes aún los tratan de forma distinta hoy.
  2. 301 puede ser cacheado agresivamente. Navegadores e intermediarios pueden almacenar un 301 más tiempo del que esperas, así que “lo arreglé” puede no propagarse a tu portátil.
  3. 302 históricamente significaba “temporal”, pero los clientes fueron creativos. Ese lío es por lo que existen 307 y 308: preservan la semántica del método de forma más predecible.
  4. HSTS cambia el juego. Una vez que un navegador aprende “usa siempre HTTPS para este host”, puede que ni siquiera intente HTTP de nuevo, complicando la depuración.
  5. Los proxies inversos volvieron ambigua la pregunta “¿es esto HTTPS?”. Si TLS termina upstream, tu Nginx ve HTTP y debes confiar en cabeceras o PROXY protocol.
  6. El encabezado Host es poderoso y peligroso. Permite hosting virtual, pero confiarlo a ciegas puede crear redirecciones abiertas o caos de canonicidad.
  7. Los CDNs introdujeron modos de “SSL flexible”. Esos modos hablan HTTPS con el navegador pero HTTP con tu origen—terreno perfecto para bucles HTTP→HTTPS.
  8. La selección del servidor por defecto es una trampa silenciosa. Nginx elegirá un “default_server” si nada coincide; una redirección allí puede secuestrar hosts no relacionados.
  9. La canonicalización no es solo SEO. Afecta cookies (alcance por dominio), CORS, URIs de redirección OAuth y la afinidad de sesión.

Una cita, porque funciona en operaciones: La esperanza no es una estrategia.Gordon R. Sullivan

Guía rápida de diagnóstico (comprueba esto primero)

Cuando alguien avisa “sitio caído, demasiadas redirecciones”, no empieces por mirar la configuración de Nginx como si te debiera dinero.
Empieza respondiendo tres preguntas con rapidez y con pruebas.

1) ¿El bucle es por host, por esquema o por la app?

  • Usa curl -IL para ver la cadena de redirecciones y ver qué cambia en cada salto.
  • Si alterna entre http/https: problema de esquema (o de cabeceras del proxy).
  • Si alterna entre www/no-www: conflicto de host canónico (Nginx vs app vs CDN).
  • Si se queda en la misma URL pero sigue 301/302: la app podría redirigir a sí misma según cabeceras/cookies.

2) ¿Dónde se termina TLS?

  • Si TLS termina en Nginx: usa $scheme y escucha en 443 con certificados.
  • Si TLS termina en un balanceador/CDN: Nginx ve HTTP, así que las redirecciones deben basarse en X-Forwarded-Proto o PROXY protocol.
  • Si no estás seguro: revisa ss -lntp y la configuración de tu plataforma upstream.

3) ¿Quién es el propietario de la canonicalización?

  • Elige exactamente uno: borde Nginx, CDN o aplicación.
  • Si tanto Nginx como la app redirigen a lo “canónico”, eventualmente crearás un bucle por una mínima discrepancia (puerto, host, barra final, esquema).

Haz esas tres cosas y dejarás de adivinar. La mayoría de bucles colapsan en un único mal “if ($scheme = http)” detrás de un proxy, o reglas enfrentadas de www.

Establecer la verdad de base: quién termina TLS y quién decide lo canónico

En Debian 13, Nginx se comporta igual que en Debian 12, pero tu entorno probablemente no.
“Debian 13” suele ser sinónimo de “VM nueva, valores por defecto nuevos, comportamiento del LB nuevo, automatización de certificados nueva, configuración copiada nueva”.

Decide tu política de URL canónica (ponla por escrito)

Elige un host canónico y un esquema canónico. Ejemplos:

  • https://example.com (sin www) es canónico; todo http y www redirigen allí.
  • https://www.example.com es canónico; todo no-www redirige allí, y todo http redirige a https.

No hagas que “ambos funcionen” mediante pilas separadas de redirecciones. Haz que lleguen a uno solo.

Decide dónde viven las redirecciones

Mi opinión fuerte: realiza las redirecciones canónicas en el borde que controles con más fiabilidad. Para muchas implantaciones Debian/Nginx,
ese es Nginx mismo. Para setups con CDN pesados, el CDN puede hacerlo—si eres disciplinado y quitas la misma lógica de Nginx/app.

Si el framework de la aplicación también fuerza HTTPS (común en Django, Rails, Laravel, Spring), o bien:

  • Desactívalo en la app y deja que Nginx lo haga, o
  • Deja que la app lo gestione y asegúrate de que Nginx no le haga la guerra (sin reescrituras conflictivas, cabeceras forwarded correctas).

Tareas prácticas (comandos, salidas, decisiones)

Arreglar esto será más rápido si recopilas señales. Abajo hay tareas prácticas que puedes ejecutar en Debian 13.
Cada tarea incluye: comando, salida de ejemplo, lo que significa y qué decisión tomar.

Tarea 1: Confirma que Nginx está en ejecución y qué configuración está cargada

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"

Significado: Nginx está activo. No estás depurando un servicio muerto.
Decisión: Procede a inspeccionar la configuración; si no está activo, arregla primero los errores de arranque del servicio.

Tarea 2: Valida la sintaxis de la configuración (no lo saltes)

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

Significado: La sintaxis está bien. Los bucles de redirección son lógicos, no de tiempo de parseo.
Decisión: Pasa a la “configuración efectiva” y a la cadena de redirección.

Tarea 3: Vuelca la configuración efectiva y encuentra las reglas de redirección aplicadas

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/*;
}

Significado: Nginx incluye tanto conf.d como sites-enabled.
Decisión: Busca en el volcado completo return 301, rewrite, $scheme y X-Forwarded-Proto.

Tarea 4: Localiza todas las declaraciones de redirección y los bloques server donde viven

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;

Significado: Hay una redirección por defecto en el puerto 80 y una redirección de host canónico en 443.
Decisión: Esto puede estar bien—pero si estás detrás de un proxy que habla HTTP con Nginx, $scheme será http y puede disparar bucles.

Tarea 5: Observa la cadena de redirección desde el propio servidor (evita tu navegador)

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/

Significado: Mismo objetivo repetido: https://example.com/ redirige a sí mismo. Eso no es “HTTP→HTTPS”. Es una condición lógica que se dispara en solicitudes HTTPS.
Decisión: Inspecciona el bloque server 443 en busca de una redirección incondicional (a menudo vía un if erróneo, una variable o comportamiento upstream de la app).

Tarea 6: Comprueba si 443 está realmente terminando TLS en este 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))

Significado: Nginx escucha en ambos puertos localmente. Eso sugiere que la terminación TLS está aquí (a menos que un proxy túnel difiera).
Decisión: Si estás detrás de un balanceador, confirma si se conecta al origen en 443 o 80.

Tarea 7: Verifica qué certificado está sirviendo Nginx (confirma que golpeas la caja correcta)

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

Significado: La terminación TLS está efectivamente en Nginx y el SNI coincide.
Decisión: Concéntrate en la lógica de 443 de Nginx y en redirecciones upstream de la app en lugar de la terminación TLS externa.

Tarea 8: Inspecciona el access log buscando patrones repetidos 301/302

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"

Significado: El mismo cliente está siendo redirigido repetidamente en la misma ruta.
Decisión: Esto probablemente sea una redirección incondicional en el server 443, o una app que redirige y Nginx vuelve a redirigir.

Tarea 9: Activa temporalmente un formato de log de depuración de redirecciones

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

Significado: Ahora registras esquema y cabeceras forwarded para cada petición.
Decisión: Reproduce una vez, luego lee el log de depuración para ver qué señal impulsó la redirección.

Tarea 10: Reproduce y lee el log de depuración de redirecciones

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"

Significado: Nginx cree que el esquema es https, pero aun así devolvió 301 a la misma URL.
Decisión: Esa redirección no depende del esquema; es una regla de canonicalización errónea o un rewrite/return en el server/location equivocado.

Tarea 11: Lista los sitios habilitados y confirma que no tienes bloques server enfrentados

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

Significado: El sitio por defecto sigue habilitado. En instalaciones nuevas suele incluir una redirección o un catch-all.
Decisión: Si tu servidor “default” hace redirecciones, puede interceptar hostnames inesperados. Desactívalo salvo que lo uses activamente.

Tarea 12: Encuentra qué bloque server responde por un hostname (las trampas default_server)

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;

Significado: Hay un default_server explícito en 80 que responderá cualquier cosa. Bien si devuelve 444/404; peligroso si redirige.
Decisión: Haz que tu server por defecto devuelva 444 o un 404 mínimo—no una redirección—a menos que controles todos los Host entrantes.

Tarea 13: Comprueba si la aplicación upstream está haciendo la redirección

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

Significado: La app está redirigiendo a HTTPS por sí misma. Si Nginx también redirige, puedes fácilmente buclear (especialmente detrás de un proxy).
Decisión: O dejas que la app maneje las redirecciones HTTPS y configuras correctamente las cabeceras forwarded, o desactivas la redirección a nivel de app y dejas que Nginx la gestione.

Tarea 14: Comprueba HSTS que haga al navegador “pelear” con tus pruebas

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

Significado: Si HSTS está presente, los navegadores forzarán HTTPS para ese host durante la duración de max-age.
Decisión: Durante un incidente de redirección, prueba con curl y hosts limpios; no confíes en el folklore de “funciona en incógnito”.

Tarea 15: Observa errores de Nginx durante recargas y manejo de peticiones

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

Significado: Sin fallos de recarga. Los logs no dirán directamente “bucle de redirección” pero mostrarán problemas de recarga de configuración y patrones de petición.
Decisión: Si las recargas fallan, arregla sintaxis/permisos primero. Si no, sigue con logs de acceso y trazas curl.

La solución correcta: una única ruta de redirección canónica

El patrón fiable es aburrido. Por eso funciona.

  • Tener un bloque server para HTTP que solo redirija al host HTTPS canónico.
  • Tener un bloque server para HTTPS del host canónico que sirva contenido (o haga proxy).
  • Opcionalmente, un bloque HTTPS para el host no canónico que redirija al canónico.
  • Nunca redirigir peticiones HTTPS a la misma URL HTTPS otra vez (sí, la gente lo hace accidentalmente con variables).
  • Detrás de un proxy inverso, no uses $scheme a menos que TLS termine en Nginx.

Por qué ocurren los bucles: host canónico + “utilidad” HTTPS chocando

Forma común del bucle:

  1. El CDN termina TLS y envía HTTP al origen.
  2. El origen Nginx dice “si el esquema es http, redirige a https”. Ve $scheme=http y redirige.
  3. El cliente sigue a HTTPS en el CDN de nuevo. El CDN repite HTTP al origen. El origen redirige otra vez. Bucle.

Otra forma del bucle:

  1. Nginx en 443 dice “si el host es www, redirige a non-www”.
  2. La app dice “si el host es non-www, redirige a www” (porque alguien puso mal una variable de entorno).
  3. Bucle. Todos “tienen razón” en su propio punto de vista.

Broma #2: Las redirecciones de Nginx son como la política de oficina—una vez que dos departamentos poseen la misma decisión, vas a dar vueltas para siempre.

Guía contundente: elige un responsable y elimina a los demás

Si Nginx es tu propietario canónico:

  • Desactiva las opciones de “forzar HTTPS” del framework, o configúralas para confiar en cabeceras proxy y no re-redirigir.
  • Haz que Nginx calcule el host canónico una vez y lo aplique de forma consistente.

Si la app es tu propietaria canónica:

  • Deja de hacer redirecciones de host/esquema en Nginx. Usa Nginx como un pipe tonto con cabeceras X-Forwarded-* correctas.
  • Asegura que la app confíe en esas cabeceras solo desde las IPs de tu proxy (la seguridad importa; los redirect open y spoofing existen).

Configuraciones de referencia de Nginx que no producen bucles

Estos patrones son intencionalmente llanos. Las configuraciones elegantes no ganan premios de uptime.
Sustituye example.com y los detalles del upstream según necesites.

Caso A: TLS termina en Nginx (la mayoría de despliegues VPS)

Canónico: https://example.com. Redirige todo lo demás allí.

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

Por qué funciona: no hay “if ($scheme …)” dentro del server 443 canónico. Las peticiones HTTPS no volverán a redirigir según el esquema.
Las redirecciones de host ocurren solo para el host no canónico.

Caso B: TLS termina en upstream (balanceador/CDN), el origen es solo HTTP

Si el balanceador conecta a Nginx en el puerto 80, tu origen no puede confiar en $scheme. Siempre será http.
En ese mundo, típicamente deberías evitar por completo las redirecciones HTTP→HTTPS en el origen y hacerlas en el borde.

Pero a veces debes imponer la canonicalización en el origen (cumplimiento, múltiples bordes, DNS corporativo desordenado). Entonces:

  • Confía en X-Forwarded-Proto solo desde los rangos IP del LB/CDN.
  • Usa un map para calcular el “esquema real” y redirige solo cuando sea realmente HTTP desde la perspectiva del cliente.
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

Advertencia: Este if es aceptable porque es un simple return a nivel de server. Aún así, prefiere redirecciones en el borde cuando sea posible.
Además: si no restringes quién puede fijar cabeceras forwarded, un cliente puede falsificarlas y causar problemas de seguridad.

Caso C: El servidor por defecto no debe redirigir (reducir radio de impacto)

Tu server por defecto existe para manejar de forma segura Host headers basura. No debe “canonicizar” de forma “útil” hacia tu dominio real.
Ahí es donde empiezas a servir el dominio de otra persona a través de tu sitio y a crear tormentas de redirecciones extrañas.

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

Significado: Nginx cierra la conexión sin respuesta. Eso está bien para hosts desconocidos.
Decisión: Mantenlo. Evita comportamientos accidentales de redirección y hace que tus reglas canónicas se apliquen solo donde corresponde.

Detrás de un balanceador/CDN: deja de fiarte de la señal equivocada

La mayoría de bucles de redirección en 2025 no son “Nginx no puede redirigir”. Son “Nginx recibe la historia equivocada sobre la petición”.
Si un proxy termina TLS, tu origen ve HTTP. Si tu origen insiste en redirigir HTTP→HTTPS, tienes una rueda de hámster.

Reconoce la firma clásica del bucle de proxy

En curl -IL, ves:

  • Location: https://example.com/… repetido
  • Estado 301/302 una y otra vez
  • Y tus logs de acceso del origen muestran solo peticiones HTTP, nunca 443

Árbol de decisión: qué hacer

  1. Si puedes: gestiona la redirección HTTP→HTTPS en el borde (LB/CDN). Desactívala en el origen. Esto es lo más limpio.
  2. Si debes hacerlo en el origen: confía en una cabecera de esquema forwarded desde el proxy, y solo desde el proxy.
  3. Si no puedes confiar en cabeceras: usa PROXY protocol de extremo a extremo (LB → Nginx) y configura Nginx en consecuencia.

Comprueba las cabeceras forwarded que llegan a Nginx

Tu log de depuración de redirecciones de la Tarea 9 ya imprime:
xfp="$http_x_forwarded_proto" y xfh="$http_x_forwarded_host".
Si están vacías, tu borde no las envía, o Nginx no está recibiendo la petición que crees que recibe.

Por qué “solo poner proxy_set_header X-Forwarded-Proto $scheme” puede estar mal

Esa línea suele copiarse en la configuración del proxy de origen. Fija X-Forwarded-Proto al esquema del origen, no al del cliente.
Si TLS termina upstream, $scheme será http. Enhorabuena, acabas de decirle a tu app “esto es HTTP” para siempre.

Si tu app fuerza HTTPS basándose en X-Forwarded-Proto, redirigirá constantemente. Si Nginx fuerza HTTPS basándose en $scheme,
también redirigirá constantemente. El bucle no es misterioso. Es obediente.

Nota de seguridad: las cabeceras forwarded son entrada del usuario

Un cliente puede enviar X-Forwarded-Proto: https directamente a tu origen si puede alcanzarlo.
Si confías en esa cabecera desde Internet abierto, puedes romper supuestos de seguridad y crear comportamientos extraños de redirección y cookies.
Restringe por red o asegúrate de que el origen no esté expuesto directamente.

Tres mini-historias corporativas (realistas, dolorosas, útiles)

Mini-historia 1: El incidente causado por una suposición equivocada

Una compañía mediana movió un portal de clientes desde un par de cajas Nginx on-premises a un balanceador gestionado frente a una nueva flota Debian.
El plan de migración tenía una frase que los condenó: “TLS se mantiene igual.” Todos asintieron porque sonaba tranquilizador.

On-prem, Nginx terminaba TLS. En el nuevo setup, el balanceador terminaba TLS y hablaba HTTP con el origen porque era “más sencillo”
y el equipo quería evitar distribución de certificados en las instancias. El origen Nginx mantuvo la regla antigua: return 301 https://$host$request_uri;
en el server de puerto 80.

El primer síntoma no fue una caída en los monitores—fue una explosión de tickets de soporte: “El login no para de refrescar.”
Algunos usuarios podían cargar la página principal (cacheada). Otros no podían autenticar porque el bucle de redirección impedía establecer cookies de sesión.
Los SRE vieron 301s en los logs pero asumieron “las redirecciones son normales; los navegadores las gestionan.”

La solución fue simple y algo humillante: quitar la redirección HTTP→HTTPS en el origen, implementarla en el balanceador,
y añadir una redirección de host canónico solo en una capa. La mejor línea del postmortem fue la más aburrida:
“Asumimos que $scheme reflejaba el esquema del cliente.” No lo hacía. Nunca lo hizo. Reflejaba el salto hacia Nginx.

Mini-historia 2: La optimización que salió mal

En otro sitio, industria distinta. El equipo decidió “optimizar” consolidando bloques server.
Un mega bloque manejaría HTTP y HTTPS, www y non-www, varios entornos,
y calcularía el destino de la redirección usando variables para mantener la config “DRY”.

Empezó inocente: un map para host canónico, una variable para esquema y un par de ifs.
Luego vino un feature flag para “modo mantenimiento” y una rewrite para rutas legacy.
Alguien añadió return 301 $canonical_scheme://$canonical_host$request_uri; dentro de una location que coincidía demasiado ancho.

En staging “funcionó” porque staging usaba un solo host y sin CDN. En producción, detrás de un proxy,
la variable de esquema canónico evaluaba a https basada en X-Forwarded-Proto—a excepción de una ruta que no tenía la cabecera
porque una regla WAF la eliminaba para ciertos agentes de usuario.

Resultado: un bucle de redirección que solo afectó a un subconjunto de clientes y solo en la ruta de checkout. El dashboard parecía verde.
Los ingresos menos verdes. La optimización ahorró quizá 40 líneas de config y costó días de respuesta a incidentes.
Hicieron rollback a bloques server separados y aceptaron la duplicación como una característica de fiabilidad.

Mini-historia 3: La práctica aburrida pero correcta que salvó el día

Un equipo financiero tenía una regla: cada cambio de Nginx que afecte redirecciones debía incluir un curl -IL grabado
para cuatro URLs: http/non-www, http/www, https/non-www, https/www. La salida se pegaba en la solicitud de cambio.
La gente se quejaba. Parecía burocracia.

Durante una renovación rutinaria de certificados, un ingeniero nuevo reactivó accidentalmente el sitio por defecto.
El server por defecto en puerto 80 redirigía al host canónico. Mientras tanto, la aplicación aplicaba otro host canónico
según su configuración. Esa combinación creó un bucle sólo para peticiones que alcanzaban el server por defecto (es decir, cualquier Host no reconocido).

El revisor de la solicitud de cambio lo detectó porque las cadenas de curl requeridas mostraban de pronto una redirección desde un hostname inesperado a producción.
El revisor preguntó: “¿Por qué el default_server redirige a cualquier lugar?” Lo arreglaron haciendo que el default server devolviera 444.

No hubo incidente. No hubo rollback nocturno. Solo un proceso aburrido haciendo su trabajo. Ese es el tipo de aburrimiento que deberías aprender a querer.

Errores comunes: síntoma → causa raíz → solución

1) Síntoma: Bucle HTTP→HTTPS solo cuando estás detrás de CDN/LB

Causa raíz: TLS termina en el CDN/LB, el origen ve HTTP, el origen redirige a HTTPS, el CDN repite.

Solución: Mueve la redirección al borde, o confía en X-Forwarded-Proto desde el proxy y base la redirección en eso.

2) Síntoma: https://example.com redirige a sí mismo (misma URL)

Causa raíz: return 301 https://example.com$request_uri; incondicional dentro del server 443 (o ubicación mal acotada).

Solución: Quita la redirección del server HTTPS canónico. Redirige solo desde el servidor HTTP o desde el servidor del host no canónico.

3) Síntoma: Alterna entre www y non-www

Causa raíz: Nginx y la aplicación no se ponen de acuerdo sobre el host canónico (o el CDN tiene su propia redirección).

Solución: Elige un propietario de la canonicalización. Desactiva las otras reglas. Verifica con curl -IL desde varios puntos.

4) Síntoma: Funciona para algunos usuarios, buclea para otros

Causa raíz: Comportamiento dividido por múltiples POPs edge, enrutado A/B, WAF que elimina cabeceras, o solo ciertas rutas activan la lógica de la app.

Solución: Añade logs para $scheme, $host, $sent_http_location y cabeceras forwarded. Compara peticiones que fallan vs las que funcionan.

5) Síntoma: Solo tu navegador falla; curl funciona

Causa raíz: 301 cacheado o HSTS en el navegador, o cookies antiguas que disparan flujos de redirección en la app.

Solución: Usa curl con -IL. Borra HSTS para el host o prueba desde un perfil/dispositivo limpio. Considera quitar HSTS temporalmente hasta estabilizar.

6) Síntoma: El bucle empezó después de habilitar el sitio “default”

Causa raíz: default_server atrapa peticiones y aplica una redirección pensada para un host específico.

Solución: Haz que el server por defecto devuelva 444/404. Asegura que los vhosts reales tengan server_name exactos y no compitan.

7) Síntoma: OAuth falla con mismatch en redirect_uri

Causa raíz: El host/esquema canónicos cambian durante el flujo; las redirecciones reescriben esquema/host y el proveedor lo rechaza.

Solución: Impón la canonicalización antes de los endpoints de autenticación, y asegura que la app vea el esquema/host correctos vía cabeceras forwarded.

8) Síntoma: Endpoints WebSocket fallan mientras las páginas normales funcionan

Causa raíz: Redirección o imposición de esquema aplicada a rutas /ws; el upstream espera upgrade y recibe 301.

Solución: Exime las ubicaciones websocket de las redirecciones; asegúrate de que la canonicalización ocurra a nivel de server, no dentro de ubicaciones websocket.

Listas de comprobación / plan paso a paso

Plan paso a paso para arreglar el bucle sin adivinar

  1. Captura la cadena de redirección.
    Ejecuta curl desde un entorno limpio:

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

    Decisión: Si la cadena alterna esquema/host, sabes contra qué estás luchando.

  2. Decide host + esquema canónicos por escrito. Ejemplo: “canónico es https://example.com”.

    Decisión: Todo lo demás redirige a eso, una sola vez.

  3. Determina la terminación 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))
    

    Decisión: Si Nginx no escucha en 443, deja de escribir redirecciones basadas en $scheme en el origen.

  4. Quita propietarios duplicados de redirecciones. Revisa la config de la app por “force SSL” / “canonical host” y desactívalos o alinéalos.

    Decisión: Un solo propietario. No dos.

  5. Haz que el server por defecto sea inofensivo. Devuelve 444 o un 404 mínimo.

    Decisión: Reduce el radio de impacto de Host headers mal dirigidos.

  6. Usa bloques server separados para redirecciones. Bloque HTTP de redirección; bloque HTTPS canónico que sirve; bloque HTTPS opcional para host no canónico que redirige.

    Decisión: Prefiere claridad sobre ingenio.

  7. Añade logging temporal de depuración de redirecciones si no estás seguro. Captura esquema/host/Location en logs.

    Decisión: No discutas opiniones; discute líneas de log.

  8. Recarga y vuelve a probar con curl. Siempre valida la config y recarga con suavidad.

    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
    

    Decisión: Si el bucle persiste, está upstream (CDN/app), no en la sintaxis de Nginx.

Lista operativa (consérvala para tu yo futuro)

  • Para cualquier cambio de redirección, registra cadenas curl para 4 puntos de entrada: http/https × www/non-www.
  • Confirma quién termina TLS y dónde se aplican las redirecciones.
  • Asegura que default_server no redirija a un dominio real.
  • Detrás de proxies: verifica la presencia de X-Forwarded-Proto y los límites de confianza.
  • No despliegues simultáneamente CDN “force HTTPS” y origen “force HTTPS” a menos que entiendas completamente el comportamiento de saltos.
  • Sé cauto con HSTS; no lo actives hasta que las redirecciones sean estables y verificadas.

Preguntas frecuentes

1) ¿Por qué mi navegador dice “Demasiadas redirecciones” pero curl a veces funciona?

Los navegadores cachean 301s y aplican HSTS. Curl normalmente no cachea entre ejecuciones.
Si antes serviste HSTS, tu navegador puede forzar HTTPS y nunca alcanzar tu lógica de redirección HTTP como esperas.

2) ¿Debo usar 301 o 302 para redirecciones canónicas?

Para canonicalización estable (www→non-www, http→https), usa 301 o 308. Si estás experimentando activamente, usa 302/307 para evitar caching permanente.
En producción, sé deliberado: 301 puede quedarse más tiempo del que deseas.

3) ¿Está bien usar “if” en Nginx para redirecciones?

Un if simple que devuelve inmediatamente con return a nivel de server suele estar bien.
La mala reputación viene de rewrites complejos y lógica anidada. Prefiere bloques server separados siempre que puedas.

4) ¿Cuál es la forma más rápida de ver la cadena de redirección?

Usa curl -IL. Añade --max-redirs 20 si hace falta. Busca qué cambia en cada salto: esquema, host o ruta.

5) Estoy detrás de un CDN en “Flexible SSL”. ¿Por qué causa bucles?

Flexible SSL a menudo significa: navegador↔CDN es HTTPS, CDN↔origen es HTTP.
Tu origen ve HTTP y redirige a HTTPS. El CDN sigue usando HTTP hacia el origen. Bucle.
Arregla usando TLS completo al origen o moviendo las redirecciones al CDN y desactivándolas en el origen.

6) ¿Puede el framework de la aplicación causar el bucle aunque la config de Nginx parezca correcta?

Absolutamente. Muchos frameworks redirigen según el esquema/host percibidos.
Si la app ve X-Forwarded-Proto: http (o nada), puede redirigir a HTTPS. Si Nginx también redirige, o el host difiere, se crea el bucle.

7) ¿Por qué es arriesgado dejar el sitio por defecto habilitado?

Porque default_server atrapa hosts no coincidentes. Si redirige a tu dominio canónico,
puedes accidentalmente servir o redirigir tráfico para Host headers inesperados, incluidos errores tipográficos y sondas hostiles.
Haz que el server por defecto devuelva 444/404 en su lugar.

8) ¿Cómo sé si el CDN/LB está enviando X-Forwarded-Proto?

Regístralo en Nginx ($http_x_forwarded_proto) y comprueba. Si está vacío, o no se envía, o se elimina, o no estás golpeando el camino que crees.
No asumas; verifica en logs.

9) ¿Debo activar HSTS mientras arreglo redirecciones?

No. Arregla las redirecciones primero. Luego activa HSTS cuando estés seguro de que HTTPS se sirve consistentemente en el host canónico.
HSTS es un dispositivo de compromiso—genial cuando tienes razón, molesto cuando te equivocas.

10) Arreglé Nginx pero los usuarios aún reportan bucles. ¿Ahora qué?

Revisa el borde (CDN/LB) reglas de redirección, configuraciones de cache y si existen múltiples orígenes.
Luego comprueba redirecciones a nivel de app. Finalmente, considera el cacheo del navegador/HSTS. El bucle puede estar fuera de la caja que acabas de editar.

Conclusión: siguientes pasos que realmente reducen el tiempo de incidente

Los bucles de redirección son simples en teoría y molestos en la práctica porque múltiples capas se sienten con derecho a “arreglar” la URL.
Tu trabajo es hacer que una capa sea la adulta en la sala y despedir a las demás de esa responsabilidad.

Pasos prácticos:

  1. Ejecuta curl -IL para http/https × www/non-www y guarda la cadena en tus notas del incidente.
  2. Decide la URL canónica (host + esquema) y elige un único responsable de las redirecciones.
  3. Si estás detrás de un proxy/CDN, deja de usar $scheme como “esquema del cliente” salvo que TLS termine en Nginx.
  4. Haz que default_server devuelva 444/404, no redirecciones.
  5. Recarga Nginx de forma segura (nginx -t luego systemctl reload) y vuelve a probar la cadena hasta que converja en 1–2 saltos.
  6. Sólo tras la estabilidad: considera habilitar HSTS, con intención y un plan de reversión.

Una vez hecho eso, “Demasiadas redirecciones” vuelve a ser lo que debe ser: una solución rápida, no un rasgo de personalidad.

← Anterior
Turbo Boost: cómo las CPU engañan sus fichas técnicas (legalmente)
Siguiente →
La era del botón de reinicio: la solución más honesta en informática

Deja un comentario