Problema

En entornos donde Autoloader (Authelia) protege aplicaciones como Jellyfin a través de un reverse proxy, es frecuente encontrarse con la pantalla de carga infinita y el mensaje Redirection was determined to be unsafe and aborted. El navegador bloquea la redirección porque la URL de destino no pasa las validaciones de seguridad del propio Authelia. El síntoma se manifiesta después de una autenticación exitosa: Authelia muestra su página de login, pero al intentar acceder a la aplicación protegida el flujo se detiene.

Este patrón no es exclusivo de Jellyfin; cualquier servicio que dependa de auth_request en Nginx Proxy Manager (NPM) y que reciba la URL de retorno a través del parámetro rd puede colapsar si la URL no coincide exactamente con lo que Authelia considera “seguro”. El problema suele aparecer cuando se combinan:

  • Cloudflare Tunnel (o cualquier túnel que reescriba el esquema/host)
  • NPM configurado con proxy_set_header personalizados
  • Dominios wildcard y redirecciones entre sub‑dominios

Causa

  1. Desajuste entre authelia_url y el host recibido por Authelia
    Authelia valida que la URL de retorno (rd) pertenezca al dominio configurado en default_redirection_url o a los dominios listados en access_control.rules. Si el proxy envía Host distinto (por ejemplo, jelly.sampledomain.com en vez de authelia.sampledomain.com), la validación falla.

  2. Esquema incorrecto (http vs https)
    Cuando el túnel de Cloudflare termina en NPM con HTTPS pero la petición interna a Authelia se hace por HTTP, la URL construida por NPM ($scheme://$http_host$request_uri) lleva http. Authelia interpreta que la redirección lleva a un esquema no seguro y la aborta.

  3. Cabecera X-Forwarded-Proto mal propagada
    Si el proxy no reenvía X-Forwarded-Proto: https, Authelia asume que la petición original fue HTTP y genera una URL de retorno con http. Los navegadores modernos bloquean la transición de https a http en redirecciones automáticas.

  4. Uso de auth_request_set $target_url $scheme://$http_host$request_uri sin normalizar
    $http_host incluye el puerto si está presente (por ejemplo, jelly.sampledomain.com:443). Authelia no reconoce el puerto y descarta la URL.

  5. Cloudflare Tunnel elimina la cabecera CF-Connecting-IP y la reemplaza por la IP del túnel
    Cuando NPM confía en real_ip_header CF-Connecting-IP, la IP del cliente se pierde y la política de “one_factor” puede no aplicarse, provocando un ciclo de redirección.

Solución

1. Normalizar la URL de retorno en NPM

Reemplaza la construcción de $target_url por una versión que fuerce https y elimine el puerto:

set $target_url https://$host$request_uri;

Esto garantiza que Authelia siempre reciba una URL con esquema seguro y sin puerto inesperado.

2. Asegurar la propagación de X-Forwarded-Proto

Añade explícitamente la cabecera antes de la directiva auth_request:

proxy_set_header X-Forwarded-Proto $scheme;

Si el túnel termina en HTTP pero el cliente usa HTTPS, fuerza la variable:

map $http_x_forwarded_proto $forwarded_proto {
    default $http_x_forwarded_proto;
    ""      $scheme;
}
proxy_set_header X-Forwarded-Proto $forwarded_proto;

3. Ajustar authelia_url y default_redirection_url

En configuration.yml de Authelia, usa la URL pública completa del proxy:

authelia_url: 'https://authelia.sampledomain.com'
default_redirection_url: 'https://jelly.sampledomain.com'

Asegúrate de que cualquier sub‑dominio que pueda aparecer en rd esté cubierto por una regla domain con política one_factor o bypass.

4. Simplificar la regla auth_request

En el bloque location / del host de Jellyfin, elimina la variable $target_url y usa la variable interna $request_uri combinada con https://$host:

auth_request_set $target_url https://$host$request_uri;
error_page 401 =302 https://authelia.sampledomain.com?rd=$target_url;

5. Revisar la configuración de Cloudflare Tunnel

En el archivo de túnel (config.yml), habilita ingress con service: https://nginx-proxy-manager:443 y marca originRequest: { noTLSVerify: true } solo si el certificado interno es auto‑firmado. Evita que el túnel convierta HTTPS a HTTP antes de llegar a NPM.

6. Verificar la lista de “Known Proxies” en Jellyfin

Añade la IP del túnel de Cloudflare (<tunnel_ip>) y la IP interna de NPM (<npm_ip>) a Known Proxy. Esto evita que Jellyfin rechace la cabecera X-Forwarded-For.

Cuándo aplicar esta solución

  • Aparecen errores de redirección insegura después de una autenticación exitosa con Authelia.
  • El flujo funciona cuando se accede directamente a Authelia, pero falla al llegar a la aplicación protegida.
  • Se está usando Cloudflare Tunnel, NPM o cualquier otro túnel que termine en HTTP detrás de un front‑end HTTPS.
  • La política de access_control.rules incluye dominios wildcard y se confía en auth_request.

No es necesario aplicar estos cambios si:

  • La aplicación no depende de auth_request (por ejemplo, uso de plugin interno de Authelia).
  • Todas las peticiones llegan directamente a Authelia sin pasar por un túnel que altere el esquema.

Código

# 1. Editar el bloque de Jellyfin en NPM
cat > /etc/nginx/conf.d/jellyfin.conf <<'EOF'
server {
    listen 443 ssl http2;
    server_name jelly.sampledomain.com;

    # SSL config (omitido para brevedad)

    real_ip_header CF-Connecting-IP;

    location / {
        auth_request /authelia;
        auth_request_set $target_url https://$host$request_uri;
        error_page 401 =302 https://authelia.sampledomain.com?rd=$target_url;

        proxy_pass http://jellyfin:8096;
        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;
        proxy_set_header X-Forwarded-Host $http_host;
        proxy_set_header X-Forwarded-Uri $request_uri;
        proxy_set_header X-Forwarded-Ssl on;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
    }

    location /authelia {
        internal;
        proxy_pass http://authelia:9091/api/verify;
        proxy_set_header Host $http_host;
        proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
        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;
        proxy_pass_request_body off;
        proxy_set_header Content-Length "";
    }
}
EOF

# 2. Reiniciar NPM
docker exec -it nginx-proxy-manager nginx -s reload

Verificación

  1. Accede a https://authelia.sampledomain.com. Inicia sesión.
  2. Cuando la página redirija a https://jelly.sampledomain.com, verifica que la barra de direcciones muestra https y que no aparece el mensaje de redirección insegura.
  3. En los logs de Authelia (/config/notification.txt o stdout del contenedor) busca entradas Authentication succeeded y Redirect URL: https://jelly.sampledomain.com/....
  4. En los logs de NPM, confirma que la petición a /authelia devuelve 200 y que no hay líneas 401 seguidas de 302 con rd= que contenga http://.

Notas adicionales

  • Cuando se usan sub‑dominios wildcard, siempre declara una regla explícita en access_control.rules para cada sub‑dominio que pueda aparecer en rd. Authelia no interpreta *.example.com en la lista de dominios permitidos.
  • Si el túnel de Cloudflare está configurado en modo “http2” pero el backend NPM solo escucha en HTTP, habilita proxy_set_header X-Forwarded-Proto https; de forma estática.
  • En entornos con varios proxies (por ejemplo, Cloudflare + NPM + Traefik), la cadena de cabeceras X-Forwarded-For puede crecer. Usa real_ip_recursive on; y limita la lista de set_real_ip_from a las IPs de tus proxies para evitar que una IP externa sea tratada como cliente.
  • La opción “Force SSL” de NPM no es necesaria cuando el túnel ya garantiza HTTPS; sin embargo, habilitar strict-transport-security en el bloque server ayuda a evitar que navegadores intenten cargar recursos por HTTP.