You changed “one small setting”—maybe toggled Cloudflare SSL, forced HTTPS, or flipped www—and now the site bounces like a pinball: browser says Too many redirects, login won’t stick, and your uptime monitor is sending poetry.
Redirect loops are rarely mysterious. They’re just distributed systems doing what you told them to do, in four different places, in the wrong order. Let’s unwind it with production-grade diagnostics, not wishful clicking.
What “Too many redirects” actually means
A browser hits a URL, gets a 301/302/307/308 redirect, follows it, and keeps getting redirected until it hits a limit (often around 20–30 hops). The error is the browser protecting itself from an infinite loop. The loop can happen at multiple layers:
- Application: WordPress thinks it should be HTTPS or should be www, so it issues redirects.
- Web server: Nginx/Apache rewrite rules force HTTPS or canonical host, often with a regex that catches everything.
- Edge/CDN: Cloudflare “Always Use HTTPS,” Transform Rules, Page Rules, or Workers redirect before origin is even touched.
- Load balancer / reverse proxy: TLS termination + missing proxy headers makes the origin think requests are HTTP even when client is HTTPS.
Loops are usually one of these patterns:
- HTTP ↔ HTTPS ping-pong: Edge says “go HTTPS,” origin says “no, go HTTP.”
- www ↔ apex ping-pong: One layer forces www, another forces non-www.
- Scheme + host crossfire: HTTPS non-www → HTTPS www → HTTP www → HTTP non-www → …
- Cookie/session loops: especially around
/wp-adminor WooCommerce checkout when secure cookies and scheme detection disagree.
Here’s the reliable mental model: a redirect loop is conflicting canonicalization policy spread across systems that don’t share state.
Redirect loop topology: where the loop is born
Redirect debugging is faster when you map the request path:
- Client → Cloudflare edge (rules, SSL mode, HSTS behavior, cache)
- Cloudflare → Origin (scheme Cloudflare uses to connect, headers it adds)
- Origin front door (Nginx/Apache vhost rules and rewrites)
- PHP/WordPress (SiteURL/Home,
wp_redirect(), plugins,is_ssl()logic) - Back out (Location headers get rewritten? cached? mixed?)
When you see “Too many redirects,” you’re not looking for “the setting.” You’re looking for two competing opinions about the canonical URL. Kill one opinion.
Fast diagnosis playbook
If you’re on-call, you don’t have time for philosophy. Do this in order.
1) Observe the redirect chain from outside
Use a tool that shows each hop, scheme, host, and status code. Your goal: identify the toggle point where it flips (http→https or www→apex).
2) Identify whether Cloudflare is in the loop
If responses include Cloudflare headers (or your DNS is orange-cloud proxied), assume Cloudflare can be rewriting or caching redirects. Determine the configured SSL mode and any “force HTTPS” features.
3) Bypass Cloudflare to test origin behavior
Hit the origin directly (or via hosts override) with the same Host header. If the origin alone loops, fix origin/WordPress. If origin is clean, the loop is at Cloudflare (or between Cloudflare and origin due to SSL mode mismatch).
4) Decide on one canonical URL policy
Pick exactly one canonical host (www or apex) and exactly one scheme (HTTPS). Then enforce it in one place only—preferably edge or web server, not WordPress plus three plugins.
5) Verify proxy headers and WordPress scheme detection
If TLS terminates before the origin, WordPress must learn the original scheme via headers like X-Forwarded-Proto. Without that, WordPress thinks it’s HTTP and “helpfully” redirects to what it believes is correct.
6) Purge caches that remember the wrong redirect
Cloudflare can cache redirects. So can browsers. So can WordPress caching plugins. Your fix can be correct and still look broken until caches are purged or bypassed.
Interesting facts and context (because history repeats)
- Fact 1: The HTTP
Hostheader (virtual hosting) became mainstream in the late 1990s; before that, many servers assumed one site per IP. Host-based redirects got dramatically more common after that. - Fact 2:
301(Moved Permanently) is routinely cached by browsers and intermediaries. A bad 301 can haunt you longer than a bad 302. - Fact 3: Cloudflare’s “Flexible” SSL mode exists for sites without origin TLS, but it’s also a classic way to create HTTP↔HTTPS loops when the origin forces HTTPS.
- Fact 4: WordPress historically stored canonical URLs in two options—
siteurlandhome—and mismatching them is still one of the fastest ways to invent a loop. - Fact 5: HSTS doesn’t redirect using server responses; it upgrades requests inside the browser. That’s great for security and terrible for “why is it still going to https?” confusion.
- Fact 6: Redirect status codes changed with HTTP/1.1 expectations:
307and308were introduced to preserve method semantics (POST staying POST). Some proxies still mishandle this in edge cases. - Fact 7: Early “force SSL in admin” WordPress patterns predate common TLS-terminating proxies; many snippets assume the origin itself sees HTTPS and break behind modern load balancers.
- Fact 8: CDNs started as static accelerators; now they ship full request/response logic (Workers, edge rules). That’s power—and also three extra places to accidentally redirect.
One operational quote worth keeping on a sticky note:
paraphrased idea — John Allspaw: “Blameless postmortems work because they focus on how the system made failure possible, not who pushed the button.”
Practical tasks: commands, expected output, and decisions
These tasks are the backbone of real diagnosis. Each includes (a) command, (b) what the output means, (c) the decision you make next. Run them from a shell you control.
Task 1: Get the redirect chain (headers only)
cr0x@server:~$ curl -sS -I -L -o /dev/null -w '%{url_effective}\n%{http_code}\n' https://example.com
https://www.example.com/
200
Meaning: With -L, curl followed redirects. The effective URL is where you landed. HTTP code at the end is final response.
Decision: If you never reach 200 and curl errors with “maximum redirects followed,” capture the full hop list next (Task 2). If you land on a different host/scheme than intended, find where canonicalization is happening.
Task 2: Print every hop (Location headers)
cr0x@server:~$ curl -sS -D - -o /dev/null http://example.com | sed -n '1,20p'
HTTP/1.1 301 Moved Permanently
Date: Fri, 26 Dec 2025 12:00:00 GMT
Location: https://example.com/
Server: cloudflare
CF-RAY: 8abc1234abcd1234-LHR
Meaning: You can see whether the redirect is generated by Cloudflare (Server header, CF-RAY) or origin.
Decision: If Cloudflare is issuing the redirect, inspect Cloudflare rules/SSL mode first. If not, go to origin configs.
Task 3: Ask curl to show the whole redirect trace
cr0x@server:~$ curl -sS -o /dev/null -w '%{http_code} %{redirect_url}\n' -I https://example.com
301 https://www.example.com/
Meaning: The server returned a redirect and curl tells you the next URL.
Decision: If redirect_url flips scheme/host unexpectedly, identify which layer is responsible by bypassing Cloudflare (Task 6/7).
Task 4: Check whether the browser is doing HSTS upgrades (from server side, infer)
cr0x@server:~$ curl -sS -I https://example.com | grep -i strict-transport-security
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Meaning: HSTS is enabled. Browsers may force HTTPS even if you type HTTP.
Decision: If you’re testing HTTP behavior, don’t trust a normal browser session. Use curl, a fresh browser profile, or temporarily remove HSTS once the site is stable (carefully).
Task 5: Resolve DNS to confirm you’re actually using Cloudflare
cr0x@server:~$ dig +short example.com
104.21.12.34
172.67.98.76
Meaning: Those look like Cloudflare anycast IPs (not always, but often). If you see origin IPs, you may not be proxied.
Decision: If proxied, Cloudflare settings matter. If DNS points to your origin, focus on origin/WordPress.
Task 6: Bypass Cloudflare by hitting origin IP with Host header
cr0x@server:~$ curl -sS -I --resolve example.com:80:203.0.113.10 http://example.com
HTTP/1.1 301 Moved Permanently
Location: https://example.com/
Server: nginx
Meaning: --resolve forces curl to talk to the origin IP while keeping Host: example.com. This is the cleanest “is Cloudflare the problem?” test.
Decision: If the origin itself redirects in a loop (repeat for HTTPS too), fix origin/WordPress. If origin returns 200 cleanly but Cloudflare path loops, fix Cloudflare config.
Task 7: Test origin HTTPS directly (certificate might not match)
cr0x@server:~$ curl -sS -I -k --resolve example.com:443:203.0.113.10 https://example.com
HTTP/1.1 200 OK
Server: nginx
Meaning: -k ignores certificate validation. You’re testing behavior, not PKI correctness.
Decision: If HTTPS-to-origin returns 200 but Cloudflare in “Flexible” mode connects over HTTP, you’ve found the mismatch. Switch Cloudflare SSL mode (details later).
Task 8: Inspect Cloudflare-to-origin scheme assumptions via headers at origin
cr0x@server:~$ sudo tail -n 3 /var/log/nginx/access.log
198.51.100.23 - - [26/Dec/2025:12:01:02 +0000] "GET / HTTP/1.1" 301 169 "-" "Mozilla/5.0" "https"
198.51.100.23 - - [26/Dec/2025:12:01:03 +0000] "GET / HTTP/1.1" 301 169 "-" "Mozilla/5.0" "http"
Meaning: Many Nginx formats include $scheme or forwarded proto fields. If you see alternating “http” and “https” for the same client journey, you’re in ping-pong territory.
Decision: Fix the canonical scheme enforcement in one layer, and ensure forwarded-proto is honored by WordPress.
Task 9: Verify WordPress SiteURL and Home (WP-CLI)
cr0x@server:~$ cd /var/www/html
cr0x@server:~$ wp option get siteurl
http://www.example.com
cr0x@server:~$ wp option get home
https://example.com
Meaning: These must not fight each other. Here they disagree on both scheme and host.
Decision: Decide the canonical URL (say https://example.com) and set both to that (Task 10). Also check any hardcoded redirects in server config.
Task 10: Fix WordPress SiteURL/Home safely (WP-CLI)
cr0x@server:~$ wp option update siteurl 'https://example.com'
Success: Updated 'siteurl' option.
cr0x@server:~$ wp option update home 'https://example.com'
Success: Updated 'home' option.
Meaning: WordPress now agrees with itself. That removes one major redirect source.
Decision: Re-test redirect chain (Task 1/2). If loop persists, it’s elsewhere (Cloudflare rules or web server).
Task 11: Find hardcoded WP_HOME/WP_SITEURL in wp-config.php
cr0x@server:~$ grep -nE "WP_HOME|WP_SITEURL" /var/www/html/wp-config.php
82:define('WP_HOME', 'https://www.example.com');
83:define('WP_SITEURL', 'https://www.example.com');
Meaning: These constants override database options. WP-CLI updates won’t matter if these are set.
Decision: Edit them to the canonical URL or remove them if you want DB control. Then re-test.
Task 12: Check Cloudflare SSL mode symptoms by observing Location header scheme
cr0x@server:~$ curl -sS -I https://example.com | egrep -i 'HTTP/|location:|server:'
HTTP/2 301
location: http://example.com/
server: cloudflare
Meaning: You requested HTTPS, Cloudflare replied with a redirect to HTTP. That’s almost always a rule or mis-canonicalization at edge/origin.
Decision: Check Cloudflare “Always Use HTTPS,” Redirect Rules, and SSL mode. Also check origin is not rewriting Location headers oddly.
Task 13: Nginx: list server blocks and look for return/rewrite
cr0x@server:~$ sudo nginx -T 2>/dev/null | egrep -n "server_name|return 301|rewrite" | head
123: server_name example.com www.example.com;
140: return 301 https://$host$request_uri;
Meaning: You have a blanket redirect to HTTPS using $host. If another layer changes host, you can loop.
Decision: Canonicalize host explicitly (choose one) instead of reflecting $host, and ensure Cloudflare settings align.
Task 14: Apache: locate RewriteRule forcing scheme/host
cr0x@server:~$ sudo apachectl -t -D DUMP_VHOSTS | head
VirtualHost configuration:
*:80 example.com (/etc/apache2/sites-enabled/000-default.conf:1)
*:443 example.com (/etc/apache2/sites-enabled/default-ssl.conf:2)
cr0x@server:~$ grep -RIn "RewriteRule|RewriteCond|Redirect " /etc/apache2/sites-enabled | head
/etc/apache2/sites-enabled/000-default.conf:12:RewriteCond %{HTTPS} !=on
/etc/apache2/sites-enabled/000-default.conf:13:RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
Meaning: This forces HTTPS based on Apache’s notion of %{HTTPS}. Behind a proxy, Apache may not see it as on.
Decision: Teach Apache about forwarded proto or terminate TLS at Apache. Otherwise it will keep redirecting even for already-HTTPS clients.
Task 15: Confirm what WordPress thinks the request scheme is (PHP snippet via wp-cli eval)
cr0x@server:~$ wp eval 'var_export([ "is_ssl" => is_ssl(), "https_server" => $_SERVER["HTTPS"] ?? null, "xfp" => $_SERVER["HTTP_X_FORWARDED_PROTO"] ?? null ]);'
array (
'is_ssl' => false,
'https_server' => NULL,
'xfp' => 'https',
)
Meaning: WordPress sees X-Forwarded-Proto: https but is_ssl() is still false, depending on server/PHP env handling.
Decision: Implement trusted proxy handling so WordPress interprets forwarded proto correctly (details in WordPress/proxy section). If you can’t do that safely, enforce canonicalization at edge and keep origin consistent.
Task 16: Verify Cloudflare cache isn’t serving an old redirect
cr0x@server:~$ curl -sS -I https://example.com | egrep -i 'cf-cache-status|age|location|http/'
HTTP/2 301
location: https://www.example.com/
cf-cache-status: HIT
age: 834
Meaning: Cloudflare is serving a cached redirect. Your origin might already be fixed.
Decision: Purge relevant cache at Cloudflare, and ensure the new canonical redirect is correct before reintroducing caching.
Joke 1: Redirect loops are the only kind of “infinite scalability” most WordPress sites achieve without trying.
WordPress-specific culprits: SiteURL, Home, cookies, and admin
1) SiteURL vs Home mismatch
WordPress uses two URLs that sound like synonyms because they were invented in different eras for slightly different purposes:
siteurl: where WordPress core files live (wp-admin, wp-includes).home: the public-facing site root.
In most installs they should be identical. If they diverge in scheme or host, WordPress can “correct” itself with redirects. When a proxy/CDN is involved, that correction can collide with other corrections.
Do: set both to the canonical URL unless you truly run WordPress core in a subdirectory.
Avoid: fixing it only in the database while wp-config.php overrides it, or vice versa. Pick one source of truth.
2) FORCE_SSL_ADMIN and “helpful” security plugins
define('FORCE_SSL_ADMIN', true); forces SSL in the admin area. That’s fine when the origin sees HTTPS. It’s a loop generator when TLS terminates at Cloudflare or a load balancer and the origin sees plain HTTP.
Many security plugins replicate this behavior, sometimes with their own redirect logic or by setting cookies with the Secure flag while the app thinks it’s HTTP. That’s how you get login loops where credentials are accepted but the session never sticks.
3) WordPress behind a reverse proxy: make scheme detection boring
If requests arrive at the origin over HTTP but the user is on HTTPS, WordPress needs to know that the “real” scheme is HTTPS. Typically that’s via:
X-Forwarded-Proto: httpsCF-Visitor: {"scheme":"https"}(Cloudflare)
In a perfect world, your web server sets $_SERVER['HTTPS']='on' when it trusts the proxy headers. In the real world, you need to explicitly wire it up and restrict trust to known proxy IPs. Otherwise you hand attackers a “pretend I’m https” header and create security bugs.
4) Cookies and domain/secure flags
Redirect loops around login often come from cookies being scoped differently than the current hostname. Example: cookie set for www.example.com but user is redirected to example.com. WordPress can’t see the cookie, assumes you’re not logged in, redirects again, repeat.
Similarly, if the browser only sends Secure cookies over HTTPS, but something flips you to HTTP mid-chain, you lose session state and end up in a loop that looks like “can’t stay logged in.”
Cloudflare-specific culprits: SSL modes, rules, and caching
1) Cloudflare SSL/TLS modes: the loop factory
Cloudflare has multiple SSL modes between visitor↔Cloudflare and Cloudflare↔origin. The famous failure mode:
- Cloudflare “Flexible”: visitor connects via HTTPS to Cloudflare, but Cloudflare connects to origin over HTTP.
- Your origin (or WordPress) is configured to force HTTPS.
- Origin responds with
301 Location: https://example.com. - Cloudflare fetches that URL… but still uses HTTP to origin (because Flexible), receives the HTTPS redirect again, and you’ve built a loop with enterprise-grade enthusiasm.
Fix: Don’t use Flexible for WordPress if you force HTTPS at origin. Use “Full” or “Full (strict)” with a valid origin certificate. If you can’t do that yet, stop forcing HTTPS at origin and let Cloudflare handle it (but that has security tradeoffs).
2) “Always Use HTTPS” plus origin HTTPS redirects
Double-enforcement isn’t always harmful, but it increases complexity. If Cloudflare forces HTTPS and your origin also forces HTTPS based on imperfect scheme detection, you can still loop.
My preference: enforce scheme at the edge, and configure origin to serve both without redirecting (or redirect only when you’re certain the origin sees correct scheme). That reduces load and keeps canonicalization in one layer.
3) www/non-www redirect rules: choose one canonical host
Cloudflare can do host redirects via rules. Your origin can too. WordPress can too (based on its stored URL). If you let all three do it, you’re basically asking for a disagreement at 2 a.m.
Pick one:
- Option A (edge canonicalization): Cloudflare handles www↔apex and HTTP→HTTPS. Origin serves content without “helpful” redirects.
- Option B (origin canonicalization): Nginx/Apache does canonical redirects. Cloudflare just proxies and caches.
I lean toward Option A when Cloudflare is in front anyway, because it reduces origin hits and avoids PHP involvement. But do it cleanly, and avoid caching bad redirects.
4) Cached redirects and “it’s still broken” theater
Cloudflare can cache 301/302. Browsers can cache 301 aggressively. If you test once, you can poison your own results.
Operationally, you want to confirm behavior with cache-busting headers or from a clean environment. And when you fix it, you purge edge cache after verifying the new behavior is correct—purging too early just amplifies the blast radius of a wrong fix.
5) Workers and Transform Rules: the silent redirect authors
In corporate setups, the redirect loop sometimes isn’t a checkbox. It’s a Worker deployed six months ago to normalize URLs or set cookies. Workers can rewrite Location headers, change schemes, and do conditional redirects based on headers and geo. That’s powerful.
It’s also why your “but I checked Page Rules” argument won’t impress your future self.
Nginx/Apache redirect patterns that bite
Nginx: the $host mirror trap
This is common and often wrong in multi-layer deployments:
return 301 https://$host$request_uri;
If the incoming Host is sometimes www and sometimes apex (because a prior layer toggles), you’re reflecting that Host and preserving the conflict. Better: redirect explicitly to your canonical host, not whatever showed up.
Nginx: scheme detection behind proxy
When Nginx is behind a proxy, $scheme is the scheme between proxy and origin, not between user and edge. If Cloudflare connects over HTTP, $scheme is http even though the user is on HTTPS. If you write redirects based on $scheme, you might redirect HTTPS users to HTTPS (fine) but do it forever (not fine) depending on how the proxy replays.
Apache: RewriteCond %{HTTPS} !=on isn’t proxy-aware
Apache’s %{HTTPS} reflects whether Apache itself negotiated TLS. If TLS is terminated upstream, it’ll be off. Apache will then redirect to HTTPS—even though the user already is. With a proxy upstream that keeps connecting over HTTP, you can loop.
The correct solution is proxy-aware configuration (and, critically, limiting trust to proxy IPs).
Joke 2: The quickest way to find every redirect rule in your stack is to break production and wait for five teams to swear they didn’t add any.
Common mistakes: symptoms → root cause → fix
Here are the patterns I see most, with specific corrective action.
1) Symptom: HTTPS request redirects to HTTP (or flips back and forth)
Root cause: Cloudflare rule forcing HTTP, origin misconfigured canonical redirects, or “Flexible” SSL interacting with origin HTTPS enforcement.
Fix: Use Cloudflare “Full”/“Full (strict)” when origin forces HTTPS. Remove any edge rule that redirects to HTTP. Ensure origin isn’t generating HTTP Locations.
2) Symptom: apex redirects to www, and www redirects to apex
Root cause: Competing canonical host rules in different layers (Cloudflare + Nginx + WordPress). Or WordPress SiteURL/Home mismatch.
Fix: Choose one canonical host. Implement exactly one redirect rule (prefer edge or web server). Set WordPress home and siteurl to the canonical host.
3) Symptom: /wp-admin keeps redirecting to /wp-admin/ (or to login) forever
Root cause: WordPress doesn’t detect HTTPS correctly, causing secure cookie/session mismatch. Sometimes compounded by caching plugins.
Fix: Ensure forwarded proto is trusted and mapped so is_ssl() is correct. Disable caching for admin paths. Verify cookie domain and canonical host are aligned.
4) Symptom: Works when Cloudflare is paused (DNS-only), fails when proxied
Root cause: Cloudflare rules/SSL mode/cached redirect or origin expects direct TLS.
Fix: Align SSL mode (“Full strict” ideally), review edge redirect rules, purge cached redirects.
5) Symptom: Only some users see loop; others are fine
Root cause: Cached 301 in browser, HSTS, geo-specific edge logic, or multiple edge POPs with inconsistent cache.
Fix: Test with curl and a clean browser profile. Purge edge cache. Check for HSTS and avoid flipping schemes during remediation.
6) Symptom: Loop starts after enabling “Always Use HTTPS” or a WordPress SSL plugin
Root cause: Duplicated enforcement plus incorrect proxy scheme detection.
Fix: Remove one enforcer. If Cloudflare enforces HTTPS, disable WordPress SSL redirect plugins and remove redundant server redirects unless needed.
Three corporate mini-stories from the redirect trenches
Mini-story 1: The incident caused by a wrong assumption
The company had a WordPress marketing site behind Cloudflare and a managed load balancer. A developer enabled a security hardening plugin that “forced HTTPS everywhere.” The change went through because it sounded obviously correct and the staging environment didn’t use the same proxy path.
Within minutes, the homepage became unreachable in browsers. Monitoring showed a spike in 301 responses. The on-call engineer did the classic check: hit the origin directly—fine. Hit through Cloudflare—redirect loop.
The wrong assumption was subtle: they believed the origin would see HTTPS when users used HTTPS. It didn’t. TLS terminated at the load balancer, and the connection from the balancer to origin was HTTP. WordPress looked at $_SERVER['HTTPS'], saw nothing, and issued redirects to HTTPS. Cloudflare (in Flexible mode) also talked HTTP to origin and chased the redirect forever.
The fix was boring and immediate: switch Cloudflare SSL mode to Full, add a valid origin cert, and remove the plugin’s redirect feature. The long-term fix was better: a runbook that defined canonicalization ownership (edge) and a proxy header trust policy at origin.
Mini-story 2: The optimization that backfired
A team wanted to “reduce origin load” by caching more aggressively at Cloudflare. Someone enabled caching of HTML and allowed caching of 301 responses. It worked—until it didn’t.
A week later, a marketing campaign changed the canonical host from www to apex. They updated WordPress URLs and added a host redirect at the edge. It tested fine in one browser. Then support tickets arrived: half the world still got redirected to the old host, and then bounced back, hitting redirect limits.
The backfire wasn’t the idea of caching; it was caching the wrong thing at the wrong time. Cloudflare had cached old redirects at different POPs. Some clients had cached a 301 permanently as well. The system became inconsistent: different users received different “truths” about canonical URL, which fed into WordPress’s own redirection logic and cookie domains.
The fix involved targeted cache purges, temporarily switching redirect status codes to 302 during migration, and—most importantly—reducing redirect “authors” to one layer to prevent compounding drift. The postmortem recommendation was blunt: don’t cache redirects unless you’ve operationalized how you invalidate them.
Mini-story 3: The boring but correct practice that saved the day
Another org ran multiple WordPress properties with a standard edge/origin template. Nothing fancy: a canonical URL policy document, a mandatory “redirect chain check” in change review, and a tiny script that validated both www and apex across HTTP and HTTPS before deployment.
One Friday, a DNS migration moved a domain into a different Cloudflare account. The new account had a default rule: “Always Use HTTPS.” The origin already enforced HTTPS, but did it in Apache with %{HTTPS} !=on. Behind Cloudflare, Apache never saw HTTPS. That should have been a classic loop.
It didn’t become an incident because their pre-flight checks caught it immediately. The script showed the redirect chain never reached 200. They blocked the change, adjusted Apache to be proxy-aware (and limited trust), and only then enabled the edge rule.
The lesson is irritating but true: the best redirect fix is to prevent the loop from shipping. It’s not glamorous. It’s reliable.
Checklists / step-by-step plan
Step 0: Decide the canonical URL policy (don’t skip)
- Canonical scheme: HTTPS.
- Canonical host: choose either
example.comorwww.example.com. - One enforcement layer: edge or origin (not both unless you understand the interaction).
Step 1: Capture redirect matrix (4-way test)
Test all combinations and record where each lands:
- http + apex
- http + www
- https + apex
- https + www
You want all four to end at exactly one canonical URL in 0–2 hops.
Step 2: Fix Cloudflare SSL mode first when Cloudflare is in front
- If origin supports TLS: use Full (strict) when possible; otherwise Full.
- Avoid Flexible when origin forces HTTPS.
Step 3: Remove duplicate redirect logic
- Disable WordPress “force SSL” plugins if edge/origin already handles it.
- Remove conflicting Nginx/Apache rewrite rules that enforce host if Cloudflare does it.
- Keep WordPress SiteURL/Home aligned with canonical URL.
Step 4: Make origin proxy-aware (securely)
- Ensure the origin sets correct scheme based on trusted proxy headers.
- Restrict trust to known proxy IP ranges (Cloudflare IPs and your load balancer).
- Validate
is_ssl()returns true for HTTPS user traffic.
Step 5: Purge caches and retest
- Purge Cloudflare cache for the site (or at least cached redirects).
- Bypass browser cache (new profile / incognito isn’t always enough for HSTS).
- Re-run curl tests and check that final URL is stable.
Step 6: Put a guardrail in CI/change review
- Add an automated redirect chain test for the 4-way matrix.
- Fail changes if any path exceeds 2 redirects or never reaches 200/403/401 expected endpoints.
FAQ
1) Why does WordPress redirect even when I didn’t configure redirects?
WordPress enforces canonical URLs based on its stored home/siteurl and request interpretation. It can redirect to “correct” host/scheme, especially for admin paths.
2) What’s the fastest way to tell whether Cloudflare is the redirect source?
Check response headers for Cloudflare markers (like CF-RAY) and then bypass Cloudflare using curl --resolve to hit origin directly with the same Host header.
3) Is Cloudflare “Flexible” SSL ever safe for WordPress?
It can be, but it’s fragile. If your origin or WordPress ever forces HTTPS, Flexible is a loop risk. In production, “Full (strict)” is the grown-up answer.
4) I fixed it, but some users still get redirected wrongly. Why?
Bad 301s get cached by browsers and by Cloudflare. Also, HSTS upgrades happen client-side. Purge edge cache and test from a clean environment; consider temporary 302 during transitions.
5) Should I enforce www/non-www in WordPress, Nginx, or Cloudflare?
Pick one. If Cloudflare is always in front, edge enforcement is often simplest. If you sometimes bypass Cloudflare (regional, API clients), do it at origin. What you should not do is enforce it in all three.
6) Why does /wp-admin redirect loop more than the homepage?
Admin flows rely on cookies and secure flags. If WordPress thinks the request is HTTP, it may set cookies or redirects inconsistently, causing “login bounce” loops.
7) How do I handle redirects without breaking POST requests (checkout forms)?
Avoid redirecting POST if possible. If you must redirect, ensure you’re using status codes that preserve method semantics where appropriate (307/308), and validate your edge/proxy stack supports them. Often the better fix is correct canonical scheme/host before the user submits.
8) Can a plugin cause a redirect loop even if SiteURL/Home are correct?
Yes. Security, SSL, and cache plugins commonly add redirects or change cookie behavior. Disable them temporarily to isolate. If disabling fixes it, remove the plugin redirect feature and enforce canonicalization elsewhere.
9) Why does it only break on mobile networks or specific countries?
Different Cloudflare POPs can serve different cached redirects, and geo-based rules/Workers can behave differently. Treat it like a distributed cache issue: check headers, POP identifiers, and purge.
10) What’s the “one change” that fixes most loops?
Align Cloudflare SSL mode with origin reality (Full/Full strict) and make WordPress agree on canonical URL (SiteURL/Home). Then remove duplicate redirect logic.
Conclusion: next steps you can actually ship
Redirect loops happen when multiple layers argue about what the “real” URL is. Your job is to stop the argument.
- Pick one canonical URL (HTTPS + one host).
- Prove where the loop is using curl hop inspection and origin bypass tests.
- Fix the responsible layer: Cloudflare SSL mode/rules, origin rewrite rules, or WordPress SiteURL/Home and proxy headers.
- Remove duplicate redirect logic so it can’t reappear during the next “small” change.
- Purge cached redirects and validate the 4-way matrix (http/https × www/apex) lands on one URL fast.
If you do nothing else: stop using conflicting redirect authors. Production loves simplicity. It also loves not waking you up.