sfw/fix
X-Forwarded-Proto loop critical

HTTPS Redirect Loop Behind a Proxy / Load Balancer

TLS terminates at the proxy so the backend sees HTTP and its force-HTTPS rule fires on every request, looping.

What you see

ERR_TOO_MANY_REDIRECTS
The page isn't redirecting properly
Firefox has detected that the server is redirecting the request for this address in a way that will never complete.

What’s actually happening

Right after putting the site behind a load balancer, CDN, or ngrok, every HTTPS URL loops. curl -IL https://site shows an endless chain of 301s all pointing to the same https:// URL. The backend logs each hit as http (scheme is plain), even though the visitor's bar says https.

Common causes

  • The LB/CDN (AWS ALB, Cloudflare, Heroku router) terminates TLS and forwards plain HTTP to the origin, which then 'upgrades' to HTTPS again.
  • App framework reads the raw connection scheme instead of X-Forwarded-Proto (Django SECURE_SSL_REDIRECT without SECURE_PROXY_SSL_HEADER, Rails force_ssl, Laravel without trustProxies).
  • Nginx 'if ($scheme = http) return 301 https://...' on a backend that only ever receives http from the proxy.
  • Proxy isn't sending X-Forwarded-Proto at all, or the app doesn't trust it because the proxy IP isn't in the trusted list.
  • Cloudflare SSL mode set to 'Flexible', which always talks HTTP to the origin while serving HTTPS to the browser.

How to fix it

  1. Confirm the proxy sends the schemeLog or echo X-Forwarded-Proto on an inbound request. If it's missing, configure the proxy to add it (Nginx: proxy_set_header X-Forwarded-Proto $scheme). If it's present and says https, the bug is on the app side.
  2. Make the force-HTTPS rule honor the headerRedirect only when X-Forwarded-Proto is http. Django: SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO','https'). Rails/Laravel: trust the proxy. Nginx: if ($http_x_forwarded_proto = 'http') return 301 https://$host$request_uri;
  3. Fix Cloudflare SSL modeSwitch the SSL/TLS encryption mode from Flexible to Full (or Full strict) so the edge speaks HTTPS to your origin. Flexible is the single most common cause of this loop on Cloudflare.
  4. Only trust forwarded headers from your proxyWhitelist the LB/CDN source ranges as trusted proxies so an attacker can't spoof X-Forwarded-Proto. Then enable the header-aware redirect.

Stop it recurring

Set X-Forwarded-Proto at the proxy and make every force-HTTPS rule read it instead of the raw socket scheme.

Related errors