Cambiaste “una pequeña redirección” y de repente todos los navegadores gritan “Demasiadas redirecciones”. El sitio parece intentar teleportarse entre HTTP y HTTPS, o entre www y el dominio raíz, hasta que el navegador se rinde. Los registros de Nginx parecen inocentes. Tu balanceador de carga jura que no está involucrado. Y todos quieren que esto se arregle antes de la próxima reunión.
Este es el caso nº71: el bucle canónico/HTTPS. Es aburrido, común y totalmente evitable: deja de adivinar y empieza a validar lo que el cliente realmente ve, lo que Nginx cree que ve y lo que hace tu aplicación upstream.
Qué significa realmente “Demasiadas redirecciones” en términos de Nginx
Los navegadores siguen redirecciones HTTP automáticamente. Seguirán muchas—hasta que dejan de hacerlo. Cuando ves “Demasiadas redirecciones”, no es un juicio moral. Es un bucle: la petición A provoca una redirección a B; la petición B provoca una redirección a A (o a C, que eventualmente vuelve a A). El navegador se detiene para evitar un ping-pong infinito.
En el mundo de Nginx, un bucle suele venir de uno de estos patrones:
- Bucle de esquema: HTTP → HTTPS → HTTP (a menudo por confusión con cabeceras de proxy).
- Bucle de host canónico:
example.com→www.example.com→example.com(dos reglas de redirección en desacuerdo). - Bucle de normalización de rutas:
/app→/app/→/app(manejo de slash entre Nginx, la app y upstream). - Bucle de puerto: la redirección incluye
:443o:80y algo lo “corrige” de vuelta. - Capas mezcladas: redirecciones de CDN/LB más redirecciones de Nginx más redirecciones de la aplicación.
Consejo con opinión: si tienes Nginx y la aplicación realizando canonicalización, elige uno. Redirecciones en duelo son como hojas de cálculo con “fuente de la verdad” en conflicto: todos pierden.
Una realidad operativa: los bucles de redirección son diagnosticables en minutos con una terminal y disciplina. La solución más rápida no es “probar otro rewrite”. La solución más rápida es recoger la cadena de redirecciones, identificar el atributo que alterna (esquema/host/ruta) y quitar una de las dos reglas en conflicto.
Hechos y contexto que hacen este problema menos misterioso
- Las redirecciones HTTP son antiguas. Los códigos 301/302 vienen de las primeras especificaciones HTTP; “Moved Permanently” es anterior a muchas pilas web actuales.
- 301 se volvió un arma de caché. Navegadores e intermediarios pueden cachear 301 agresivamente; depurar se convierte en “Lo arreglé, pero mi portátil no.”
- 307/308 existen por una razón. 302 históricamente cambiaba POST a GET en algunos clientes; 307/308 preservan los métodos más consistentemente.
- El
returnde Nginx es más seguro querewrite. El motor de rewrite es poderoso, pero es fácil crear bucles o reescribir internamente por accidente. - Las redirecciones de host canónico nacieron por higiene SEO. Los motores penalizaban contenido duplicado; operaciones heredaron el dolor cuando SEO y TLS se apilaron.
- Los proxies cambiaron lo que significa “HTTPS”. Si TLS termina en un load balancer, Nginx ve HTTP plano a menos que le enseñes a usar
X-Forwarded-Proto. - HSTS subió la apuesta. Una vez habilitado HSTS, los clientes intentarán HTTPS independientemente de lo que creas haber configurado; una redirección HTTPS rota pasa a ser visible para el usuario de inmediato.
- Los CDN adoran “ayudar”. Muchos CDN pueden forzar HTTPS o reescribir hosts. Genial hasta que Nginx también lo hace.
Guía rápida de diagnóstico (primero/segundo/tercero)
Primero: captura la cadena de redirecciones exactamente como la ve el cliente
- Usa
curl -I -Ly registraLocation, códigos de estado y si el host/esquema cambia en cada salto. - Comprueba si el bucle alterna entre HTTP/HTTPS o entre hosts (apex vs www).
- Confirma si la redirección viene de Nginx o de algo upstream mirando cabeceras de respuesta (
Server, cabeceras personalizadas o una cabecera de trazado que añadas).
Segundo: verifica qué cree Nginx que es la petición
- Inspecciona la configuración de Nginx buscando
return 301,rewrite, bloquesify bloquesserverduplicados para el mismo nombre. - Mira los access logs con
$scheme,$hosty$http_x_forwarded_proto(añade temporalmente un formato de log de depuración si hace falta). - Si estás detrás de un proxy/CDN, confirma si confías en cabeceras reenviadas solo desde IPs conocidas.
Tercero: aisla capas
- Evita el CDN/LB si es posible (conecta directamente al origen usando un
Hostheader). - Deshabilita temporalmente las redirecciones canónicas de la aplicación o fija la URL base explícitamente para que coincida con tu política de Nginx.
- Vuelve a probar y detente solo cuando la cadena tenga a lo sumo una redirección (idealmente cero para peticiones ya canónicas).
Idea parafraseada (atribuida): “La esperanza no es una estrategia.”
— idea parafraseada comúnmente atribuida a liderazgo en fiabilidad y operaciones. Trata las redirecciones igual: valida, no improvises.
Anatomía de una redirección: host canónico, esquema y ruta
La decisión de host canónico (elige uno y aplícalo una vez)
Host canónico significa decidir si el sitio “vive” en example.com o en www.example.com. Cualquiera está bien; la indecisión no. Aplícalo en una capa—preferentemente en el borde (Nginx) porque es barato y consistente.
Dos reglas canónicas compitiendo es el clásico bucle:
- Nginx fuerza
www. - La app fuerza el dominio raíz (o un CDN lo hace).
- Resultado: rebote infinito.
La decisión de esquema canónico (HTTPS, y sé honesto al respecto)
Si terminas TLS en Nginx, $scheme es real. Si TLS termina antes de Nginx, $scheme miente (será http), a menos que establezcas y confíes en X-Forwarded-Proto o en la cabecera estandarizada Forwarded. Muchos fragmentos de “redirección a HTTPS” en internet asumen que Nginx ve TLS. Esa suposición es cómo ocurre el caso nº71.
La decisión de normalización de ruta (slashes y archivos index)
Los bucles de ruta ocurren cuando múltiples componentes normalizan de forma diferente. Nginx puede redirigir /app a /app/ por try_files o autoindex, mientras la app redirige de vuelta a /app porque su router no quiere slashes finales. Elige la política canónica e implémentala en un solo lugar.
Broma #1 (corta, relevante): Los bucles de redirección son simples pruebas de carga que no presupuestaste.
Tareas prácticas: comandos, salidas y la decisión que tomas
Estos no son “ejecuta esto y espera”. Cada tarea incluye qué observar y qué decisión impulsa. Ejecútalas en Debian 13, pero la lógica es portable.
Tarea 1: Reproducir con un rastro completo de redirecciones
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/
Qué significa: El esquema alterna HTTPS → HTTP → HTTPS. Eso es un bucle. Si ves hosts alternando en su lugar, tienes una pelea por el host canónico.
Decisión: Deja de ajustar rutas. Ve directo a la lógica de esquema/host canónico y revisa las cabeceras de proxy.
Tarea 2: Mostrar solo las cabeceras Location (firma rápida de bucle)
cr0x@server:~$ curl -sS -I http://example.com/ | sed -n 's/^Location: //p'
https://example.com/
Qué significa: El primer salto es HTTP → HTTPS. Bien por sí solo.
Decisión: Ahora prueba el endpoint HTTPS para ver quién te envía de vuelta a HTTP.
Tarea 3: Inspeccionar la respuesta HTTPS sin seguir redirecciones
cr0x@server:~$ curl -sS -I https://example.com/ | sed -n '1p;/^Location:/p'
HTTP/2 301
location: http://example.com/
Qué significa: Algo que sirve HTTPS está redirigiendo a HTTP. Ese “algo” puede ser Nginx, la app o un proxy delante.
Decisión: Identificar qué capa emitió esta respuesta (cabeceras, logs y pruebas de bypass).
Tarea 4: Confirmar qué Nginx está respondiendo (huella en cabeceras)
cr0x@server:~$ curl -sS -I https://example.com/ | grep -iE '^(server:|via:|x-cache:|x-served-by:|cf-|x-amz-)'
server: nginx
Qué significa: No es concluyente, pero si ves cabeceras específicas de CDN, no estás hablando directamente con tu Nginx.
Decisión: Si sospechas un intermediario, evítalo a continuación.
Tarea 5: Evitar DNS/CDN y golpear la IP de origen con un Host header
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/
Qué significa: Incluso al golpear el origen, HTTPS redirige a HTTP. Probablemente sea Nginx o la app upstream detrás de Nginx.
Decisión: Revisa la configuración de Nginx y las redirecciones de la aplicación upstream.
Tarea 6: Volcar la configuración activa de Nginx (sin adivinar qué archivo se incluye)
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;
...
Qué significa: Tienes la configuración renderizada incluyendo archivos include. Esto es la verdad del terreno.
Decisión: Busca en esta salida reglas de redirección y bloques de server duplicados.
Tarea 7: Encontrar directivas de redirección y condiciones sospechosas con “if”
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; }
Qué significa: Literalmente tienes redirecciones opuestas: HTTP→HTTPS en un server y HTTPS→HTTP en el otro. Ese es tu bucle.
Decisión: Elimina la redirección incorrecta HTTPS→HTTP. Reemplázala con una política canónica única.
Tarea 8: Verificar dónde aterrizan las peticiones (qué bloque server) usando los access logs
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"
Qué significa: Mismo cliente, 301 repetidos. Necesitas más contexto: host, scheme y forwarded proto.
Decisión: Añade temporalmente un formato de log de depuración que imprima las variables importantes.
Tarea 9: Añadir un log_format temporal para exponer 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
Qué significa: Has creado un access log dedicado para diagnóstico sin tocar tu formato principal.
Decisión: Recarga Nginx y ejecuta un curl; luego lee el log diag.
Tarea 10: Recargar Nginx de forma segura y confirmar que la configuración es válida
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
Qué significa: Sin errores de sintaxis; la recarga se aplicó.
Decisión: Ahora puedes confiar en que el log diag refleja el comportamiento actual.
Tarea 11: Generar una petición y leer el log diagnóstico
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/
Qué significa: Nginx ve scheme=https (así que es probable que TLS esté en Nginx), pero aun así devuelve una redirección a http://. Esa es una regla de configuración explícita, no confusión del proxy.
Decisión: Elimina cualquier redirección HTTPS→HTTP. Si necesitas HTTP internamente, manténlo interno—no redirijas a los clientes a HTTP.
Tarea 12: Si estás detrás de un proxy, verifica cómo se ve 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/
Qué significa: Cuando le dices a Nginx que el esquema original fue HTTPS, él elige HTTPS. Bien: tu lógica puede volverse consciente del proxy.
Decisión: Implementa el manejo de forwarded-proto correctamente y de forma segura (confía solo en IPs de proxy conocidas).
Tarea 13: Identificar quién escucha en los puertos 80/443 (evitar servicios fantasma)
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))
Qué significa: Nginx es el único que escucha en 80/443. Si hubieras visto otra cosa (Apache, un servidor de desarrollo), estarías depurando el proceso equivocado.
Decisión: Si los puertos están disputados, arregla eso primero. La lógica de redirección es irrelevante si el demonio equivocado responde.
Tarea 14: Inspeccionar los archivos de vhost habilitados en 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
Qué significa: Tienes un sitio habilitado. Si tienes varios archivos con el mismo server_name, espera coincidencias impredecibles.
Decisión: Asegura exactamente un bloque server canónico “poseyendo” cada hostname.
Tarea 15: Probar la selección de server de Nginx con cabeceras Host explícitas
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/
Qué significa: Ambos hosts redirigen a sí mismos en HTTPS. Si querías canonicalizar solo al dominio raíz, esto está mal (pero no necesariamente es un bucle).
Decisión: Decide el host canónico y aplícalo explícitamente (una sola redirección, no dos mundos paralelos).
Tarea 16: Validar que la app no emita sus propias redirecciones de esquema/host
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/
Qué significa: Tu app upstream está redirigiendo a HTTPS por sí misma. Eso puede estar bien, pero solo si está de acuerdo con Nginx. Si Nginx también redirige (o peor, redirige en sentido contrario), ocurren bucles.
Decisión: Elige: redirecciones canónicas en Nginx o en la app. Luego desactiva la otra.
Patrones de solución que funcionan (con la configuración correcta de Nginx)
Patrón A: TLS termina en Nginx (más simple y fiable)
Esta es la configuración más limpia: los clientes se conectan a Nginx en 443, y Nginx conoce el esquema real. Tus redirecciones pueden usar $scheme de forma segura porque refleja la realidad.
Reglas:
- Servidor en puerto 80: redirige todo al host canónico HTTPS.
- Servidor en puerto 443: sirve contenido; opcionalmente redirige hosts no canónicos al host canónico (siempre HTTPS).
- Nunca “si el esquema es https redirige a http.” Nunca. Si necesitas soportar HTTP para una red privada, hazlo en otro hostname o listener, no degradando a los usuarios públicos.
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
Por qué funciona: la decisión canónica sucede exactamente una vez por cada punto de entrada no canónico. Nunca rediriges del canónico → no canónico.
Patrón B: TLS termina upstream (load balancer/CDN), Nginx solo ve HTTP
Ahí es donde la gente tropieza. Nginx ve $scheme=http, porque el LB se conecta a Nginx por HTTP. Si escribes “si scheme es http redirige a https”, acabas forzando que el salto LB→origen también redirija. Dependiendo de cómo maneje el LB, puedes crear un bucle o al menos redirecciones innecesarias.
Lo que realmente quieres es: “Si el cliente usó HTTP, redirigir a HTTPS.” Ese “esquema del cliente” debe venir de una cabecera confiable.
Hazlo como adulto: confía en X-Forwarded-Proto solo desde los rangos IP de tus proxies/LB, y usa una variable que represente el esquema 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
Ahora usa $client_scheme en la lógica de redirección en lugar 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
Sí, usé if dentro de server. Ese es uno de los pocos lugares donde está bien. Evita if dentro de bloques location para gimnasia de rewrites; las redirecciones a nivel de server son simples y predecibles.
Regla dura: si puedes empujar redirecciones canónicas al borde (LB/CDN), hazlo allí y desactívalas en Nginx. Pero no dividas la responsabilidad entre capas a menos que disfrutes llamadas de emergencia.
Patrón C: Normalización de rutas—detén el ping-pong de slash
Si tu bucle alterna barras finales, necesitas unificar la política. Nginx puede aplicarlo, pero si tu app también lo aplica, elige uno.
Una elección común y segura es: “los directorios terminan con slash, los archivos no.” Nginx ya tiene opiniones aquí. Si el router de tu app odia las barras finales, desactiva las redirecciones implícitas de directorio en Nginx alineando try_files y rutas, o deja que la app lo gestione por completo.
Detrás de un proxy/CDN: confiar en cabeceras sin engañarte
Las cabeceras Forwarded son necesarias y peligrosas. Necesarias porque tu origen no ve el TLS del cliente. Peligrosas porque cualquier cliente puede enviar X-Forwarded-Proto: https y engañar a configuraciones ingenuas para generar enlaces HTTPS, marcar cookies como seguras o saltarse redirecciones.
Haz explícita la “confianza”
La confianza debe ser condicional según la IP de origen. En Debian 13, Nginx suele venir con valores por defecto sensatos, pero no adivinará tus límites de red. Debes ajustar set_real_ip_from a tus rangos reales de proxy.
Sabe qué cabecera emite tu proxy
Muchos sistemas usan X-Forwarded-Proto. Algunos usan la cabecera estandarizada Forwarded. Otros establecen una cabecera vendor-específica. El punto no es el nombre; es la consistencia a lo largo de la cadena.
Evita la sorpresa de “redirección absoluta”
Nginx puede generar redirecciones absolutas. Si filtras por accidente hostnames internos (como origin.internal) en la cabecera Location, tus usuarios conocerán tu mapa de red gratis. Eso no es un premio que quieras ganar.
Si ves redirecciones a un hostname equivocado, probablemente usaste $host cuando querías un dominio canónico fijo, o tu proxy está reescribiendo Host inesperadamente.
Cuando no es Nginx: la aplicación que se enfrenta a ti
Algunos frameworks redirigen a una “URL base”, fuerzan HTTPS o imponen reglas de barra final. Si la app corre detrás de un proxy y no entiende las cabeceras reenviadas, puede pensar que cada petición es HTTP y “mejorarla” a HTTPS—mientras Nginx (o el proxy) la degrada, o viceversa.
Tres movimientos tácticos que arreglan sistemas en producción:
- Fija explícitamente la URL externa de la aplicación (base URL / public URL). Muchos sistemas tienen una configuración única para esto, y evita confusión de host/esquema.
- Asegúrate de que la app respete cabeceras reenviadas solo desde IPs de proxy de confianza (misma idea que en Nginx).
- Decide quién controla las redirecciones. Si la app las necesita para el enrutamiento, deja que la app haga redirecciones de ruta y Nginx maneje solo esquema/host—o al revés. Solo no dupliques.
Broma #2 (corta, relevante): Si dos componentes ambos “aplican URLs canónicas”, eventualmente coincidirán—justo después de que acabe el incidente.
Tres microhistorias corporativas desde las trincheras de redirecciones
Microhistoria 1: Un incidente causado por una suposición errónea
La empresa tenía una arquitectura ordenada: un load balancer gestionado terminaba TLS y luego reenviaba tráfico a Nginx en el puerto 80 en una red privada. Alguien añadió una regla “simple” en Nginx: redirigir HTTP a HTTPS. La probaron haciendo curl directamente al origen por HTTP y vieron el 301 esperado. Lo desplegaron.
En producción, el load balancer conectaba a Nginx por HTTP (como diseñado). Nginx vio $scheme=http para cada petición. Así que redirigió cada petición a HTTPS. El load balancer siguió las redirecciones en sus health checks y empezó a fallarlos porque no podía negociar TLS con el origen que no hablaba TLS. El pool se vació. El sitio se quedó oscuro, aunque “la redirección era correcta”.
La solución no fue mágica: elimina las redirecciones de esquema del origen y aplica HTTPS en el load balancer. Si es necesario hacer enforcement en el origen, usa X-Forwarded-Proto y confía solo en el subnet del balancer. La lección real: no escribas redirecciones basadas en lo que el origen ve si el origen no es el endpoint TLS.
Después, añadieron una comprobación de despliegue que ejecuta curl -I contra el endpoint público y la ruta de bypass del origen, y compara la cadena de redirecciones. La próxima vez que alguien intentó “mejorar” redirecciones, el pipeline detectó la discrepancia antes que los clientes.
Microhistoria 2: Una optimización que salió mal
Otra organización intentó reducir saltos de redirección por rendimiento. Su objetivo: “nada de redirecciones nunca”. Eliminaron la redirección del puerto 80 y en su lugar configuraron el CDN para hacer la canonicalización. En papel, es más limpio: menos viajes, menos carga en origen y comportamiento consistente globalmente.
Luego desplegaron una segunda regla del CDN: normalizar www a apex. Mientras tanto, la aplicación todavía forzaba www porque una integración heredada lo esperaba. Esto no apareció en la monitorización básica porque el sitio aún “cargaba” para algunos usuarios—dependiendo del estado de caché y el hostname que ingresaran.
Lo peor fue la intermitencia. El CDN cacheó respuestas 301 en algunos POPs. Algunos usuarios vieron el bucle. Otros no. Los equipos internos “no pudieron reproducirlo”, que en lenguaje corporativo significa “lo intenté una vez y me aburrió”.
Se recuperaron declarando una política canónica y aplicándola en exactamente un sitio: el CDN. Luego deshabilitaron la imposición del host en la aplicación y fijaron la URL base de la app al dominio canónico. El rendimiento mejoró y se mantuvo, porque la política de redirección dejó de oscilar entre capas.
Microhistoria 3: Una práctica aburrida pero correcta que salvó el día
Un equipo de pagos tenía una regla: cada cambio de comportamiento en el borde requería una “instantánea de la cadena de redirección” adjunta a la solicitud de cambio. Era aburrido. La gente se quejaba. Pero forzaba claridad: qué pasa para HTTP apex, HTTP www, HTTPS apex, HTTPS www, y un camino raro con query string.
Durante una actualización rutinaria a Debian 13, la configuración de Nginx se refactorizó. Un ingeniero nuevo duplicó sin querer un server_name en dos archivos vhost habilitados. Nginx no lanzó error; simplemente eligió un bloque server para algunas peticiones según precedencia. Las redirecciones se volvieron inconsistentes.
Como tenían la costumbre de la instantánea, el revisor detectó que HTTPS www redirigía a HTTP apex—claramente incorrecto—antes de que el cambio llegara a producción. Sin heroísmos, sin bridge de incidentes, sin “haremos un postmortem”. Simplemente se rechazó el cambio y se corrigió.
La práctica no parecía innovadora. Lo era. El mejor trabajo de ops a menudo parece papeleo hasta el día en que evita un incendio.
Errores comunes (síntoma → causa raíz → solución)
1) Síntoma: ping-pong HTTP ↔ HTTPS
Causa raíz: TLS termina en un proxy, pero el origen redirige basándose en $scheme o en una cabecera reenviada no confiable. O hay una redirección explícita HTTPS→HTTP que quedó de una migración anterior.
Solución: Aplica el esquema en el endpoint TLS. Si Nginx debe aplicarlo tras un proxy, usa $http_x_forwarded_proto mapeado a un $client_scheme, y confía en él solo desde IPs de proxy.
2) Síntoma: ping-pong www ↔ apex
Causa raíz: Nginx canonicaliza a www, la app canonicaliza al dominio raíz (o viceversa). A veces el CDN canonicaliza de una forma y el origen de otra.
Solución: Elige un host canónico y aplícalo en una capa. Desactiva las redirecciones de host en la otra capa o ajusta la URL base.
3) Síntoma: solo algunos usuarios ven el bucle
Causa raíz: 301 cacheados en navegadores, CDNs o proxies corporativos. O tienes múltiples orígenes/instancias con configuraciones distintas.
Solución: Purga caches del CDN para respuestas de redirección si aplica. Prueba con un cliente limpio y curl. Verifica consistencia de configuración entre instancias.
4) Síntoma: el bucle aparece solo en una ruta específica
Causa raíz: la normalización de la barra final difiere entre Nginx y la app para esa ruta, o try_files dispara una redirección de directorio que la app rechaza.
Solución: Define una política de ruta única. O dejas que la app la maneje y evitas redirecciones de ruta en Nginx, o implementas redirecciones explícitas y consistentes en Nginx y desactivas la normalización en la app.
5) Síntoma: las redirecciones apuntan a un hostname interno o puerto equivocado
Causa raíz: uso inapropiado de $host detrás de un proxy que reescribe host, o la aplicación genera URLs absolutas basadas en la dirección interna de escucha. A veces es proxy_redirect haciendo reescritura “útil”.
Solución: Usa un dominio canónico fijo en return 301. Configura el upstream para que conozca su URL pública. Audita la configuración de proxy_redirect.
6) Síntoma: el navegador sigue en bucle aunque ya arreglaste la config
Causa raíz: 301 cacheado en el navegador, o HSTS forzando HTTPS y exponiendo otro problema de redirección, o no recargaste Nginx (pasa más de lo que cualquiera admite).
Solución: Verifica con curl desde un entorno limpio. Confirma recarga y configuración activa con nginx -T. Si HSTS está activado, asegúrate de que el endpoint HTTPS esté correcto antes de tocar el comportamiento HTTP.
Listas de verificación / plan paso a paso
Paso a paso: arreglar bucles canónicos + HTTPS de forma segura
- Escribe tu política canónica en una sola frase: “Canónico es https://example.com (sin www).” Si no puedes escribirla, no puedes aplicarla.
- Recoge cadenas de redirección para cuatro puntos de entrada: HTTP apex, HTTP www, HTTPS apex, HTTPS www. Registra códigos de estado y destinos
Location. - Evita intermediarios (CDN/LB) para ver si el origen en sí tiene el bucle.
- Renderiza la configuración activa de Nginx con
nginx -Ty busca todas las directivas de redirección. - Elimina contradicciones: quita cualquier regla que redirija del canónico → no canónico.
- Decide dónde vive la aplicación del esquema: si TLS termina en el LB/CDN, aplícalo allí, no en el origen—a menos que implementes lógica de forwarded-proto confiable.
- Decide dónde vive la imposición de host: aplícalo en el borde (Nginx o CDN) y desactiva las redirecciones de host en la app o configura su URL base en consecuencia.
- Recarga de forma segura (
nginx -tluegosystemctl reload), vuelve a probar con curl y luego con un navegador. - Elimina logs de depuración después de confirmar estabilidad. El diagnóstico es genial; el ruido permanente no lo es.
- Añade una prueba de regresión: un chequeo scriptado que verifique que esos cuatro puntos de entrada tengan como máximo una redirección y terminen en la URL canónica.
Lista operativa: antes de declarar victoria
- La URL canónica devuelve
200(o el estado esperado de la app), no301. - Las URLs no canónicas devuelven exactamente una redirección al canónico.
- El objetivo de la redirección nunca degrada HTTPS a HTTP.
- Ninguna redirección apunta a un hostname interno, IP privada o puerto inesperado.
- Los logs confirman
Hosty variables de esquema correctas. - Los health checks (LB/CDN) no siguen redirecciones que rompan la accesibilidad del origen.
Preguntas frecuentes
1) ¿Por qué el navegador dice “Demasiadas redirecciones” pero el log de errores de Nginx está tranquilo?
Porque las redirecciones no son errores para Nginx. Un 301 es una respuesta normal. El navegador detecta el bucle tras seguir redirecciones repetidas.
2) ¿Debo usar rewrite o return 301 en Nginx?
Usa return 301 para redirecciones canónicas de host/esquema. Es más claro y menos propenso a bucles. Usa rewrite solo cuando realmente necesites manipulación de URI con regex.
3) Mi TLS termina en el load balancer. ¿Es if ($scheme = http) siempre incorrecto?
Es incorrecto para decidir lo que usó el cliente, porque $scheme refleja el salto LB→origen. Usa una cabecera forwarded proto confiable y mapea a una variable como $client_scheme.
4) ¿Puedo fiarme de X-Forwarded-Proto desde Internet?
No. Cualquier cliente puede enviarlo. Confía en él solo desde rangos IP de proxy conocidos. Si no, permites que atacantes influyan comportamientos sensibles de seguridad.
5) ¿Por qué veo comportamiento distinto entre curl y el navegador?
Los navegadores cachean 301, pueden aplicar HSTS y a veces tienen DNS cacheado o service workers. Curl suele ser “fresco” a menos que lo scripts para cache. Si el navegador difiere, prueba en una ventana privada y confirma el estado de HSTS.
6) ¿Qué código de estado debería usar para HTTP → HTTPS?
Para sitios típicos, 301 está bien. Si rediriges peticiones POST y te importa preservar el método, considera 308. La consistencia importa más que la moda.
7) Arreglé el bucle, pero ahora me redirige al hostname equivocado. ¿Por qué?
Probablemente porque usaste $host en la redirección y la cabecera Host entrante no es la que pensabas (proxy reescribe, dominios alternativos). Para canonicalización, prefiere un dominio fijo en el return.
8) ¿Cómo evito bucles de barra final?
Elige una política y aplícala una sola vez. Si tu app quiere “sin barra final”, desactiva comportamientos de Nginx que la añadan implícitamente y configura la app para generar enlaces consistentes. Si Nginx la controla, haz la redirección explícita y asegura que la app no revierta.
9) ¿Debian 13 cambia algo respecto a las redirecciones de Nginx?
No fundamentalmente. Los modos de fallo son los mismos. Lo que cambia en la práctica es que las actualizaciones a menudo reordenan includes o habilitan nuevos archivos de sitio, aumentando la posibilidad de bloques server duplicados y redirecciones en conflicto.
Conclusión: siguientes pasos para evitar incidentes repetidos
Los bucles de redirección no son misteriosos. Son políticas contradictorias ejecutadas fielmente. Tu trabajo es eliminar la contradicción.
Siguientes pasos que rinden inmediatamente:
- Declara una URL canónica (esquema + host) y aplícala en una capa.
- Haz que Nginx sea consciente del proxy solo cuando sea necesario, y solo con cabeceras reenviadas confiables.
- Añade una comprobación de cadena de redirección a los despliegues: cuatro puntos de entrada dentro, un endpoint canónico fuera.
- Mantén las redirecciones aburridas: usa
return, evita rewrites ingeniosos y borra reglas antiguas de migración cuando ya no hagan falta.
Si haces esto una vez, correctamente, dejarás de ver el caso nº71 aparecer durante actualizaciones, cambios de CDN o ese “pequeño ajuste SEO” de un viernes por la tarde que siempre llega a producción.