sfw/fix
URL redirects to itself critical

URL Redirects Back to Itself (Self-Redirect)

A single URL returns a 3xx whose Location is the same URL, so it loops on the first hop and never loads.

What you see

curl -IL https://example.com/about
HTTP/2 301
location: https://example.com/about
HTTP/2 301
location: https://example.com/about
... (ERR_TOO_MANY_REDIRECTS)

What’s actually happening

The tightest possible loop: A redirects to A. One curl -I shows a 301 whose Location is byte-for-byte the URL you requested, so the browser bounces against the same address until it aborts with ERR_TOO_MANY_REDIRECTS. The usual culprit is a force-HTTPS or force-www rule firing on a request that's already HTTPS/www — because the app sees plain HTTP behind a proxy and "upgrades" a request that was never down-level. A rewrite rule matching its own output does the same thing.

Common causes

  • A force-HTTPS rule redirects to https:// while the backend, behind a TLS-terminating proxy, reads the connection as http on every request — so it redirects an already-HTTPS URL to itself.
  • A RewriteRule whose target also matches its own pattern, so the rewritten URL re-enters the rule and emits the identical Location.
  • A force-www (or force-non-www) rule that checks the wrong host variable and fires even when the host already matches.
  • Cloudflare SSL set to "Flexible" plus a server-side https redirect — the edge always sends http to origin, origin redirects to https, repeat.
  • A CMS canonical-redirect that normalizes a URL to the value it already has (e.g. a trailing-slash rule that adds a slash to a URL that already ends in one).

How to fix it

  1. Confirm Location equals the request URLcurl -I https://example.com/about and compare the location: header to the URL you asked for. If they're identical, it's a self-redirect — the rule firing isn't conditional on anything actually changing.
  2. Make force-HTTPS read the forwarded schemeBehind a proxy/CDN the socket is http; trust X-Forwarded-Proto instead. Nginx: redirect only `if ($http_x_forwarded_proto = http)`. Django: SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO','https'). Laravel: add the LB to TrustProxies. Now the redirect only fires when the visitor was truly on HTTP.
  3. Fix Cloudflare SSL modeIf you're on Cloudflare, switch SSL/TLS from Flexible to Full (or Full strict) so the edge speaks HTTPS to your origin. Flexible guarantees a self-loop the moment the origin has any http→https redirect.
  4. Make the rewrite stop matching its own outputAdd a condition so the rule skips already-correct requests — e.g. only redirect when HTTPS is off (RewriteCond %{HTTPS} off) or when the host doesn't already equal the target. A rule with no exit condition will always re-match what it just produced.
  5. Recheck and clear cachesRe-run curl -IL; you want a single 200 (or one 301 to a genuinely different URL). Purge any redirect/full-page cache so a cached self-redirect doesn't outlive the fix.

Stop it recurring

Every force-HTTPS/force-www redirect should fire only when the request truly differs from the target — test it behind your real proxy, not just locally.

Related errors