ERR_TOO_MANY_REDIRECTS means two redirect rules are arguing — each thinks the other URL is wrong and tries to redirect to its preferred version. The browser hops between them until it gives up. Almost always a layered-redirect problem: HTTPS forced at proxy AND origin, CDN rule AND .htaccess rule, plugin AND server config. The fix is identifying which two systems are conflicting and disabling the duplicate.
curl -IL --max-redirs 10 -v https://example.com/affected-page 2>&1 | \ grep -E "< HTTP|< Location|> Host"Shows each hop's status and target. Look for the URL that appears twice — that's where the loop kicks in.
# Cloudflare "Always Use HTTPS" page rule: ON
# Origin nginx config:
server {
listen 80;
return 301 https://$host$request_uri;
}
# But Cloudflare connects to origin on port 80 (Flexible SSL mode)
# Origin sees HTTP request, redirects to HTTPS
# Cloudflare receives HTTPS redirect, applies its own HTTPS rule (already HTTPS, no change)
# But if Cloudflare connects via HTTP and origin's redirect target is HTTPS via Cloudflare...
# Result: depends on Cloudflare SSL mode
# Fix: set Cloudflare SSL to "Full" or "Full (Strict)" — origin connection uses HTTPS
# Then disable origin's HTTP→HTTPS redirect, let Cloudflare handle it
# OR keep origin's redirect and use Cloudflare "Off" SSL (not recommended)
# Cloudflare page rule: www.example.com → example.com (301)
# .htaccess on origin:
RewriteCond %{HTTP_HOST} ^example\.com$
RewriteRule (.*) https://www.example.com/$1 [R=301,L]
# Cloudflare strips www → origin sees non-www → origin adds www → Cloudflare strips again. LOOP.
# Fix: pick one place to do canonicalisation. Disable at the other.
# WordPress Redirection plugin: /old-page → /new-page (301) # .htaccess (forgotten from previous setup): Redirect 301 /new-page /old-page # Plugin and .htaccess each pointing the other's source at its target. LOOP. # Fix: check .htaccess for orphan Redirect/RewriteRule lines. # Delete the ones that conflict with plugin-managed redirects.
# Cloudflare "Flexible SSL": connects to origin via HTTP
# Origin checks: if $scheme != "https" return 301 https://...
# Origin always sees HTTP from Cloudflare, always 301s, infinite loop
# Fix: use X-Forwarded-Proto header instead of $scheme
if ($http_x_forwarded_proto != "https") {
return 301 https://$host$request_uri;
}
# Origin trusts the header that Cloudflare sets. No loop.
# redirect-ownership.md # - HTTPS upgrade: Cloudflare → "Always Use HTTPS" page rule # - www canonicalisation: Cloudflare → page rule www.* → root domain # - Trailing slash: Origin nginx → return 301 with slash # - Custom moves: WordPress Redirection plugin # # DO NOT add redirects in .htaccess or origin server config without updating this doc.
Schedule weekly scans. Any URL resolving in 3+ hops is a regression. The Redirect Checker alerts on chain length and loops.
Some sites redirect mobile vs desktop. If detection misfires, you get loops only for certain User-Agents.
# Test with curl mimicking different agents curl -IL -A "Mozilla/5.0 (iPhone; ..." https://example.com/page curl -IL -A "Mozilla/5.0 (X11; Linux x86_64) ..." https://example.com/page # If only one User-Agent loops, the mobile detection logic has a bug.
Auth or A/B test redirects that depend on cookies can loop when cookies aren't being set properly.
# Reproduce without cookies curl -IL --cookie-jar /dev/null https://example.com/page # If clean without cookies but loops with cookies, check cookie-setting on the redirect target.