If you’ve ever hit wp-admin, watched the spinner, and then gotten punted back to the login page like it never happened—welcome to the world of WordPress redirect loops. The browser says “too many redirects” or WordPress says “You are being redirected.” Your users say “site’s down.” You say words unfit for a change record.
This failure mode is rarely “WordPress is broken.” It’s almost always your request’s idea of HTTP vs HTTPS not matching WordPress’s idea, or a cookie that never sticks. The fix is usually small. The hunt can be long if you don’t treat it like an SRE incident: reproduce, observe, narrow, change one thing, verify.
What the error really means (and why it loops)
WordPress redirect loops are a disagreement between layers. Your browser follows instructions like: “go to https://…”, then “no, go to http://…”, then “actually, https again.” Or it logs in, but the authentication cookie doesn’t match the host/scheme/path WordPress expects, so WordPress decides you’re not logged in and redirects you back to login, which tries again, which fails again. Round and round.
WordPress itself issues redirects from several places:
- Site URL canonicalization (Home/SiteURL mismatch, or wrong scheme/host): WordPress tries to enforce what it believes the site’s URL is.
- Admin SSL enforcement (FORCE_SSL_ADMIN, wp-admin rules): login and admin paths get forced to HTTPS.
- Pluggable auth cookies (COOKIE_DOMAIN, secure cookies, cookie path): if cookies don’t match, it behaves like you never logged in.
- Proxy/CDN decisions (X-Forwarded-Proto, CF-Visitor, TLS offload): WordPress sees “HTTP” at the origin while the user sees “HTTPS” in the browser.
- Server rewrites (.htaccess, Nginx return 301, app-level redirects): multiple redirect engines competing for control.
In production, you almost always have at least three systems that think they’re “the one true place for redirects”: the CDN, the load balancer/reverse proxy, and the origin web server. WordPress is a fourth. If two of them disagree about scheme or host, you get a loop. If they agree but the cookie doesn’t persist, you get a login loop that looks similar, but isn’t.
One dry rule that pays rent: pick exactly one layer to own HTTP→HTTPS and www→non-www canonical redirects. Everyone else should pass the request through and set headers accurately.
Fast diagnosis playbook
1) Confirm what kind of loop it is (scheme/host vs cookie)
If the redirect chain alternates between http and https, or between www and apex, you’re in canonicalization hell. If it keeps returning you to /wp-login.php after a successful POST, you’re probably in cookie hell.
2) Observe the redirect chain from outside, not from your browser
Use curl to see Location headers. Your browser is helpful until it isn’t; it hides details behind “too many redirects.”
3) Identify the SSL termination point
Where does TLS end? CDN? Load balancer? Nginx on the host? If the origin sees HTTP while the client sees HTTPS, you must pass scheme information through headers and teach WordPress to trust them.
4) Check WordPress’s own truth: Home and SiteURL
WordPress stores these in the database and sometimes in wp-config.php. If they don’t match the actual public URL, WordPress will “fix” it with redirects forever.
5) Only then look at plugins, caching, and weird rewrite rules
Plugins do cause loops, but most “plugin” loops are still scheme/cookie mismatches. Disable plugins last, not first. You want to debug systems, not perform exorcisms.
One quote, because it fits: “Hope is not a strategy.” — General Gordon R. Sullivan
Redirect loop taxonomy: SSL vs cookies vs caching
A) SSL termination / proxy header mismatch
Classic setup: client connects via HTTPS to a CDN or load balancer, which connects to the origin via HTTP. WordPress runs on the origin and sees $_SERVER['HTTPS'] as off and the server port as 80. WordPress builds URLs as http://, then the edge forces https://, then WordPress forces http://, and you’ve built a perpetual motion machine that produces only misery.
Fix pattern: ensure the edge sets X-Forwarded-Proto: https (and/or a vendor header), and ensure the origin and WordPress interpret it correctly. Then make sure only one layer enforces HTTPS redirects.
B) Cookie not set / not returned (the login loop)
You enter credentials, it “logs in,” then you’re back at the login screen. That’s WordPress not seeing a valid auth cookie on the next request. Causes include:
- Secure cookies being set over HTTPS but the site is accessed over HTTP (or vice versa).
- Wrong cookie domain (COOKIE_DOMAIN mismatch between
example.comandwww.example.com). - Proxy rewriting Host headers; WordPress sets cookies for the wrong domain.
- SameSite policies interacting with embedded login flows (less common for standard wp-admin, more common for SSO-ish flows).
- Cache layer caching the login page or redirect responses. Yes, people do this. No, it doesn’t end well.
C) Conflicting canonical redirects (www, trailing slashes, multisite mapping)
WordPress likes a canonical URL. So does your CDN. So does your reverse proxy. So do five different plugins that want to “help SEO.” When multiple components each “correct” the URL differently—adding/removing www, forcing a trailing slash, normalizing uppercase, or pushing /index.php—you can build a loop without involving SSL at all.
D) Caching and “optimization” side effects
Redirect responses are cacheable. Some caches will store 301/302 aggressively when told to, and some admins accidentally cache them via overbroad rules. If a cache holds onto a bad Location header, you can fix the origin and still see the loop from certain geos or only for certain users.
Joke #1: Redirect loops are like office politics—everyone insists they’re “just aligning,” and nobody is accountable for the actual outcome.
Practical tasks (commands, outputs, decisions)
These tasks are meant to be run from an admin shell on the origin host, or from a bastion. If you can’t run commands, adapt them to your environment. The point is the same: gather evidence, then decide.
Task 1: Inspect the redirect chain with curl
cr0x@server:~$ curl -sS -D - -o /dev/null -L --max-redirs 10 https://example.com/wp-admin/ | sed -n '1,120p'
HTTP/2 302
location: https://example.com/wp-login.php?redirect_to=https%3A%2F%2Fexample.com%2Fwp-admin%2F&reauth=1
set-cookie: wordpress_test_cookie=WP%20Cookie%20check; path=/; secure; HttpOnly
HTTP/2 302
location: https://example.com/wp-admin/
set-cookie: wordpress_logged_in_abc123=cr0x%7C1735900000%7Ctoken; path=/; secure; HttpOnly
HTTP/2 302
location: https://example.com/wp-login.php?redirect_to=https%3A%2F%2Fexample.com%2Fwp-admin%2F&reauth=1
What it means: We’re bouncing between wp-login and wp-admin even after a logged_in cookie is set. That strongly suggests the cookie isn’t being returned or isn’t valid for subsequent requests.
Decision: Switch to cookie-focused checks: domain, secure flag, proxy Host headers, and caching of auth endpoints.
Task 2: Check for http↔https flipping
cr0x@server:~$ curl -sS -I http://example.com/ | egrep -i '^(HTTP|location)'
HTTP/1.1 301 Moved Permanently
Location: https://example.com/
cr0x@server:~$ curl -sS -I https://example.com/ | egrep -i '^(HTTP|location)'
HTTP/2 301
location: http://example.com/
What it means: Your edge redirects HTTP→HTTPS, but your origin (or app) redirects HTTPS→HTTP. That’s a clean two-node loop.
Decision: Pick the canonical scheme (it’s 2025; it’s HTTPS), then disable the conflicting redirect at one layer and fix forwarded-proto handling.
Task 3: Confirm what WordPress thinks the site URL is (WP-CLI)
cr0x@server:~$ cd /var/www/html
cr0x@server:/var/www/html$ sudo -u www-data wp option get home
http://example.com
cr0x@server:/var/www/html$ sudo -u www-data wp option get siteurl
http://example.com
What it means: WordPress believes the site is HTTP. If the public site is HTTPS, WordPress will keep trying to “correct” things depending on settings and plugins.
Decision: Update home and siteurl to HTTPS (after confirming TLS is correctly terminated and the proxy headers are correct).
Task 4: Set Home/SiteURL safely with WP-CLI
cr0x@server:/var/www/html$ sudo -u www-data wp option update home 'https://example.com'
Success: Updated 'home' option.
cr0x@server:/var/www/html$ sudo -u www-data wp option update siteurl 'https://example.com'
Success: Updated 'siteurl' option.
What it means: You’ve changed the canonical URLs in the DB. WordPress will now generate HTTPS URLs.
Decision: Immediately re-test redirect chains. If it still loops, you likely have an upstream redirect rule or proxy mismatch.
Task 5: Check wp-config.php for hardcoded URL overrides
cr0x@server:~$ sudo egrep -n "WP_HOME|WP_SITEURL|FORCE_SSL_ADMIN|COOKIE_DOMAIN|COOKIEPATH|SITECOOKIEPATH" /var/www/html/wp-config.php
82:define('WP_HOME','http://example.com');
83:define('WP_SITEURL','http://example.com');
What it means: Even if you fixed the DB, these constants override it. That’s a common “why did nothing change?” trap.
Decision: Change those to HTTPS or remove them and manage via DB (pick one control plane; don’t mix casually).
Task 6: Verify what scheme the origin thinks it’s serving
cr0x@server:~$ sudo -u www-data php -r 'var_export([$_SERVER["HTTPS"]??null,$_SERVER["SERVER_PORT"]??null,$_SERVER["HTTP_X_FORWARDED_PROTO"]??null,$_SERVER["HTTP_HOST"]??null]);'
array (
0 => NULL,
1 => '80',
2 => 'https',
3 => 'example.com',
)
What it means: TLS is terminated upstream (HTTPS is null, port 80), but the proxy sends X-Forwarded-Proto=https. WordPress won’t automatically trust this unless the stack is configured to.
Decision: Ensure the reverse proxy passes the header consistently, and configure WordPress/PHP to treat X-Forwarded-Proto as authoritative (carefully—only when you trust the proxy).
Task 7: Inspect Nginx proxy headers (common cause)
cr0x@server:~$ sudo nginx -T 2>/dev/null | egrep -n "proxy_set_header (Host|X-Forwarded-Proto|X-Forwarded-For)|return 301|listen 443|listen 80" | sed -n '1,120p'
45: listen 80;
61: return 301 https://$host$request_uri;
90: listen 443 ssl http2;
132: proxy_set_header Host $host;
133: proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
134: proxy_set_header X-Forwarded-Proto $scheme;
What it means: If the origin is behind another proxy, $scheme might be http even when the client used HTTPS. Then WordPress sees forwarded-proto=http and generates HTTP redirects.
Decision: If TLS terminates at the edge, set X-Forwarded-Proto https (or pass through the edge’s proto header) between proxies, not $scheme.
Task 8: Check Apache rewrite rules (.htaccess) for conflicting redirects
cr0x@server:~$ sudo sed -n '1,160p' /var/www/html/.htaccess
# BEGIN WordPress
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
# END WordPress
What it means: This forces HTTPS at the origin. If your edge already forces HTTPS but talks to origin over HTTP, Apache will redirect to HTTPS on the origin host—sometimes the wrong host, sometimes a private name, sometimes just loops with the edge.
Decision: Move HTTPS enforcement to the edge (recommended) or terminate TLS on the origin. Don’t half-do both.
Task 9: Confirm Cloudflare/Flexible SSL-type behavior (header evidence)
cr0x@server:~$ curl -sS -I https://example.com/ | egrep -i "^(cf-|server:|location:|x-forwarded-proto:)"
server: cloudflare
cf-cache-status: DYNAMIC
What it means: You’re behind Cloudflare (or a similar CDN). If the CDN is set to a mode that connects to origin via HTTP while presenting HTTPS to clients, you must ensure the origin stack understands the real scheme.
Decision: Prefer “Full”/end-to-end TLS modes and consistent forwarded-proto handling. Also ensure the CDN isn’t rewriting Location headers in surprising ways.
Task 10: Verify cookies are being sent back (curl cookie jar)
cr0x@server:~$ curl -sS -c /tmp/cj.txt -b /tmp/cj.txt -L --max-redirs 5 https://example.com/wp-login.php -o /dev/null -D - | egrep -i "set-cookie:|location:|HTTP/"
HTTP/2 200
set-cookie: wordpress_test_cookie=WP%20Cookie%20check; path=/; secure; HttpOnly
cr0x@server:~$ grep -v '^#' /tmp/cj.txt | sed -n '1,5p'
example.com FALSE / TRUE 0 wordpress_test_cookie WP%20Cookie%20check
What it means: The cookie is marked Secure and scoped to example.com and /. If you access via www.example.com or via HTTP, it won’t be sent. Also, if a proxy rewrites the Host, the cookie domain might be wrong.
Decision: Ensure the public hostname matches what WordPress uses, and avoid bouncing between www and apex during login.
Task 11: Confirm Host header preservation through proxies
cr0x@server:~$ curl -sS -H 'Host: example.com' -I http://127.0.0.1/ | egrep -i "^(HTTP|location|set-cookie)"
HTTP/1.1 301 Moved Permanently
Location: https://example.com/
What it means: Your local vhost responds correctly when Host is set. If you instead see redirects to an internal hostname, your proxy/origin config is leaking internal names into Location headers.
Decision: Fix vhost/server_name and proxy_set_header Host; also confirm WordPress home/siteurl matches the intended public host.
Task 12: Look for cached redirects at Nginx or a caching proxy
cr0x@server:~$ sudo egrep -R "proxy_cache|fastcgi_cache|expires|add_header Cache-Control|location = /wp-login.php|location ~ \\.php" -n /etc/nginx | sed -n '1,120p'
/etc/nginx/sites-enabled/example.conf:55: fastcgi_cache WORDPRESS;
/etc/nginx/sites-enabled/example.conf:56: add_header X-Cache $upstream_cache_status;
/etc/nginx/sites-enabled/example.conf:80: location = /wp-login.php { include fastcgi_params; }
/etc/nginx/sites-enabled/example.conf:81: fastcgi_cache WORDPRESS;
What it means: Someone is caching PHP responses, including wp-login.php. That can cache redirects and Set-Cookie headers, producing bizarre loops that vary per user.
Decision: Disable caching for login/admin endpoints and for responses that set auth cookies. Cache the anonymous front end, not authentication flows.
Task 13: Verify WordPress is not mis-detecting HTTPS (quick mu-plugin fix test)
cr0x@server:~$ sudo install -d /var/www/html/wp-content/mu-plugins
cr0x@server:~$ sudo tee /var/www/html/wp-content/mu-plugins/00-proxy-https.php >/dev/null <<'PHP'
<?php
// Trust X-Forwarded-Proto only if coming from known proxies (adjust CIDRs).
$trusted = ['10.0.0.0/8','172.16.0.0/12','192.168.0.0/16'];
$remote = $_SERVER['REMOTE_ADDR'] ?? '';
function ip_in_cidrs($ip, $cidrs) {
foreach ($cidrs as $cidr) {
[$subnet,$bits] = explode('/', $cidr);
$ip_long = ip2long($ip);
$sub_long = ip2long($subnet);
$mask = -1 << (32 - (int)$bits);
if (($ip_long & $mask) === ($sub_long & $mask)) return true;
}
return false;
}
if (ip_in_cidrs($remote, $trusted)) {
if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
$_SERVER['HTTPS'] = 'on';
$_SERVER['SERVER_PORT'] = 443;
}
}
PHP
What it means: This is a pragmatic test to force HTTPS detection when behind trusted proxies. It’s not “the” solution for every environment, but it quickly proves whether scheme detection is the culprit.
Decision: If loops disappear, implement a cleaner long-term solution in your proxy/app stack and keep the trust boundary tight.
Task 14: Check database-level Home/SiteURL without WP-CLI (for broken CLI)
cr0x@server:~$ mysql -N -e "SELECT option_name, option_value FROM wp_options WHERE option_name IN ('home','siteurl');"
home http://example.com
siteurl http://example.com
What it means: Same as WP-CLI, but works when PHP/CLI integration is busted.
Decision: Update these values (carefully) if they’re wrong. If you’re in multisite, stop and read the multisite FAQ below first.
Task 15: Confirm admin cookies aren’t being stripped by a security header policy
cr0x@server:~$ curl -sS -I https://example.com/wp-login.php | egrep -i "set-cookie|content-security-policy|strict-transport-security|x-frame-options"
strict-transport-security: max-age=31536000; includeSubDomains; preload
What it means: HSTS is fine and usually helpful. But if you set includeSubDomains and you have legacy subdomains still on HTTP, those clients will be forced to HTTPS and may hit broken endpoints—sometimes feeding a redirect mess.
Decision: Keep HSTS, but only after you’ve verified TLS coverage for all required subdomains. Don’t set preload lightly.
Joke #2: If your WordPress is stuck in a redirect loop, congrats—your site has achieved perpetual motion, just not the useful kind.
Three corporate-world mini-stories
1) The incident caused by a wrong assumption: “The load balancer handles HTTPS, so the app knows it’s HTTPS”
They’d just migrated from a single VM with Apache to a “proper” stack: CDN → load balancer → Nginx → PHP-FPM. The change request said “no functional changes.” That line has ended more evenings than any outage.
The site came up. The homepage looked fine. Then editors tried to log in and got the “You are being redirected” loop. The on-call engineer did the standard dance: clear browser cookies, try incognito, disable a couple plugins. Nothing. Meanwhile, the marketing team discovered new ways to refresh a page.
The underlying assumption was subtle: because the client used HTTPS, the app would know it was HTTPS. But TLS was terminated at the load balancer, and the origin traffic was plain HTTP. Nginx set X-Forwarded-Proto using $scheme, which was http at that hop. WordPress saw HTTP, set non-secure-ish expectations, and bounced the admin redirect.
The fix was unglamorous and immediate: set X-Forwarded-Proto based on the incoming header from the load balancer, not the local scheme; add a small WordPress HTTPS-detection snippet with a strict proxy trust boundary; then standardize redirect ownership at the edge only. After that, the login loop vanished.
The postmortem takeaway was even less glamorous: “assume nothing about scheme; observe headers at every hop.” They added a one-line header dump endpoint for internal debugging and locked down who could change proxy header logic. The outage wasn’t heroic; it was a missing fact.
2) The optimization that backfired: caching wp-login.php “for performance”
Another organization had a traffic spike problem. The front end was WordPress and the cache hit rate was… aspirational. A well-meaning performance initiative rolled out aggressive Nginx fastcgi_cache rules. The objective was sensible: reduce PHP load, reduce latency, avoid scaling costs.
In staging it looked great. Then production happened: intermittent login failures, some users seeing other users’ post-login redirects, and a bizarre redirect loop that only occurred in certain regions. It didn’t fail consistently, which made it feel like “WordPress is flaky.” It wasn’t.
They had cached responses from /wp-login.php and some redirects from /wp-admin/. That meant cached Location headers and cached Set-Cookie behavior, which is basically “randomly reuse old authentication choreography.” If you want to weaponize a cache against your own auth system, this is an efficient way to do it.
The resolution: carve out explicit no-cache rules for /wp-login.php, /wp-admin/, and any request/response that sets auth cookies. They also made the cache key include scheme/host and a few headers that mattered. Performance dipped a bit. Reliability skyrocketed. That’s a trade you take every time.
Afterwards, they kept caching—but only for anonymous GETs and only for content pages. The team also learned to treat 301/302 caching as a change-controlled capability. Redirect caching can be a feature; it can also be an outage in a trench coat.
3) The boring but correct practice that saved the day: one canonical redirect layer + recorded redirect tests
A third team ran WordPress for multiple business units, behind a CDN and two layers of proxies. They’d been burned before, so they did something deeply unfashionable: they made a rule and documented it.
The rule: the CDN owns HTTP→HTTPS and www normalization. The origin never issues scheme/host redirects, only application-level ones (like permalinks). All proxies must preserve Host and set forwarded proto consistently. WordPress’s Home/SiteURL must match the public URL, and any overrides in wp-config.php are forbidden unless there’s a ticket that explains why.
They also built a tiny redirect-chain regression test as part of deployment verification. It ran curl -IL against key endpoints (/, /wp-admin/, /wp-login.php) and asserted: max 3 redirects, final scheme https, final host canonical, no bouncing between login/admin. If the chain changed, the deploy was blocked.
Months later, a vendor plugin update introduced a new canonical redirect behavior that would have clashed with the CDN’s rules. The test caught it before customers did. They didn’t need heroics; they needed the boring gate that says “no, not today.”
Common mistakes: symptom → root cause → fix
1) “Too many redirects” only on HTTPS
- Symptom: HTTP works (or redirects once), HTTPS loops.
- Root cause: Origin thinks requests are HTTP because TLS terminates upstream; WordPress generates HTTP URLs and redirects.
- Fix: Pass and trust
X-Forwarded-Proto=httpscorrectly; set WordPress home/siteurl to https; ensure only one layer enforces scheme redirects.
2) Login loop: credentials accepted, then back to wp-login.php
- Symptom: POST to wp-login succeeds, then redirect back to login with reauth=1.
- Root cause: Auth cookie not returned (domain mismatch, secure flag mismatch, caching, or Host header rewrite).
- Fix: Ensure consistent host (www vs apex), correct
COOKIE_DOMAIN(or unset), don’t cache auth endpoints, preserve Host through proxies.
3) Alternating www and non-www in Location headers
- Symptom: Redirect chain flips between
example.comandwww.example.com. - Root cause: CDN enforces one host; WordPress home/siteurl or server rewrite enforces the other.
- Fix: Pick a canonical host, set it in one layer only, align WordPress home/siteurl, and remove competing rewrite rules.
4) Redirect loop appears only after enabling HSTS
- Symptom: Some clients get stuck; others fine.
- Root cause: HSTS forces HTTPS to subdomains that aren’t ready; a subdomain redirects to HTTP, which browsers refuse.
- Fix: Remove
includeSubDomainsuntil you have complete TLS coverage, or fix TLS for all subdomains; avoid preload until you’re certain.
5) Loop only for certain geographies / ISPs
- Symptom: Support tickets come from one region; you can’t reproduce locally.
- Root cause: Edge cache holding stale 301/302 or inconsistent CDN config by POP.
- Fix: Purge cached redirects; reduce cacheability of redirects; verify uniform config rollout across POPs.
6) Loop begins right after changing the site URL in WordPress settings
- Symptom: Admin becomes inaccessible immediately after changing URL settings.
- Root cause: New URL doesn’t match actual vhost/ingress routing; WordPress redirects you to a host that doesn’t serve WordPress.
- Fix: Update via WP-CLI or DB carefully; ensure DNS/vhost matches; temporarily add hosts file entry for testing.
7) Multisite admin loops after domain mapping changes
- Symptom: Network admin works on one domain but not mapped sites.
- Root cause: Incorrect domain mapping or cookie domain/path conflicts across subsites.
- Fix: Confirm multisite constants and domain mapping settings; align cookie configuration with mapping strategy; ensure proxy preserves Host per site.
Checklists / step-by-step plan
Step-by-step: fix an SSL/proxy redirect loop (the common case)
- Observe the redirect chain with curl. Confirm whether it flips schemes or hosts.
- Identify TLS termination: CDN, LB, origin. Draw the hop diagram. Don’t guess.
- Choose the redirect owner: usually the edge (CDN/LB) for scheme and canonical host.
- Disable competing redirects at other layers (remove conflicting Nginx/Apache return/rewrite rules; audit WordPress/plugins that force SSL).
- Fix forwarded headers: preserve Host; set X-Forwarded-Proto to the client scheme; ensure upstream-to-origin hops don’t overwrite it incorrectly.
- Teach WordPress to interpret the scheme behind proxies (via trusted proxy config/snippet), but do not trust arbitrary client headers on the public internet.
- Set WordPress home/siteurl to the canonical public URL (prefer DB or wp-config constants, not both).
- Re-test the redirect chain for: max redirects, final URL, consistent host, consistent scheme.
- Purge caches (CDN and any reverse proxies) if redirects were cached.
- Add a regression check so the next “small change” doesn’t become an incident.
Step-by-step: fix a cookie/login loop
- Reproduce with curl cookie jar to confirm cookies are being set and returned.
- Stabilize the hostname: always use the canonical host for login/admin. Don’t bounce between www and apex.
- Check cookie scope: domain and path. Avoid forcing COOKIE_DOMAIN unless you must.
- Ensure secure cookie consistency: if cookies are Secure, serve login/admin over HTTPS consistently end-to-end.
- Disable caching for auth endpoints everywhere: CDN rules, Nginx/Apache, plugins.
- Check Host header preservation through proxies; WordPress uses it in building redirects and cookies.
- Only then suspect plugins that alter login flows, security plugins, or custom SSO integrations.
Operational checklist: what to record during the incident
- Exact failing URL(s) and whether it affects anonymous users or only authenticated users.
- Redirect chain output (curl -IL with headers) from at least one external vantage point.
- Where TLS terminates and what headers are forwarded at each hop.
- Current values of home/siteurl and any wp-config overrides.
- Cache layers in the path and whether redirects are cached.
- What changed recently (CDN rule, plugin, migration, certificate renewal, load balancer policy).
Facts and historical context
- Fact 1: Early web apps often relied on the presence of server variables like
HTTPS=on; reverse proxies broke that assumption long before it became mainstream. - Fact 2: The
X-Forwarded-Protoheader emerged as a de facto standard because backend apps needed to know the original client scheme after TLS termination upstream. - Fact 3: WordPress stores
homeandsiteurlin the database, a design choice that made moves/renames easy—until you forget to update them during a migration. - Fact 4: WordPress login relies on multiple cookies (test cookie, logged_in, auth, secure_auth), and different paths/scopes can apply depending on admin vs front-end.
- Fact 5: Caches became common in front of WordPress because PHP rendering under load was expensive; caching auth flows was always a bad idea, but “always” didn’t stop anyone.
- Fact 6: HTTPS went from “nice for payments” to “default expectation” largely due to browser UX changes and search ranking incentives, which increased the frequency of HTTP/HTTPS mismatch bugs.
- Fact 7: HSTS was created to prevent SSL stripping attacks; operationally, it also means one wrong HTTPS configuration can stick around in clients longer than your patience.
- Fact 8: Redirect status codes matter: 301s can be cached aggressively by browsers and CDNs, making a temporary mistake surprisingly persistent.
- Fact 9: “Flexible SSL” style modes exist to ease onboarding, but they also normalize the dangerous pattern of HTTPS-to-edge and HTTP-to-origin, which is where many loops are born.
FAQ
1) Why does WordPress say “You are being redirected” instead of showing an error?
That message is usually WordPress trying to send a redirect (often via JavaScript/meta refresh) when it believes the request URL is wrong or auth is incomplete. The browser then hits the next URL and the cycle repeats.
2) Is this always caused by SSL?
No. SSL/proxy mismatch is common, but cookie scope issues (domain/path/secure flag), cached redirects, and conflicting www/non-www rules are equally frequent. The fastest discriminator is the redirect chain: scheme flips suggest SSL; login/admin bouncing suggests cookies.
3) Should I fix it by defining WP_HOME and WP_SITEURL in wp-config.php?
Sometimes. It’s a valid control plane, especially when you want config-as-code. But don’t mix it casually with DB-managed values; overrides can confuse future you. If you set them in wp-config.php, remove the temptation to “just change it in the UI.”
4) I’m behind a load balancer. What headers must be correct?
At minimum: preserve Host and pass X-Forwarded-Proto reflecting the client scheme. Also pass X-Forwarded-For for logging/rate-limits. Then ensure WordPress/PHP trusts these headers only from known proxy IPs.
5) Why does it work for me but not for other users?
Usually caching. A CDN POP may have cached a bad 301/302; or a browser cached a 301; or a specific path is cached with Set-Cookie stripped. Purge edge caches and verify Cache-Control on redirect responses.
6) How do I know if cookies are the problem?
If login “succeeds” but you land back on wp-login.php, it’s almost always cookies. Confirm by capturing Set-Cookie and then ensuring the cookie is returned on the next request (curl cookie jar, or browser devtools request headers).
7) Can a plugin cause redirect loops?
Yes, especially security/SSL/SEO plugins. But treat plugin blame as a last step after confirming the infrastructure isn’t lying about scheme/host. Disable plugins only after you’ve captured the redirect chain and checked home/siteurl and proxy headers.
8) What about multisite—are the fixes different?
Multisite adds more ways to get domain/cookie scope wrong. Home/siteurl may not tell the full story, domain mapping adds complexity, and cookie domain/path can differ per site. Approach it the same way—observe redirects and cookies—but validate per-site hostnames and network settings.
9) I fixed the origin but browsers still loop. Why?
Browsers cache 301s aggressively. CDNs also cache redirects if instructed. Test with a fresh client, purge caches, and consider using 302 while stabilizing a migration, then switching to 301 once you’re certain.
10) Is there a single “best” architecture to prevent this?
Yes: end-to-end HTTPS, one canonical redirect owner (usually the edge), correct forwarded headers, and explicit no-cache rules for auth/admin. It’s not exciting. That’s why it works.
Conclusion: next steps that don’t waste your afternoon
If you take only one operational habit from this: always capture the redirect chain and the cookie behavior before changing anything. Redirect loops feel chaotic, but they’re deterministic. The system is doing exactly what you told it to do—just not what you meant.
Practical next steps:
- Run the curl redirect-chain checks and classify the loop (scheme/host vs cookie).
- Align WordPress home/siteurl with the public canonical URL and remove conflicting overrides.
- Fix proxy headers and trust boundaries so the origin knows the client scheme and host.
- Make one layer own canonical redirects; disable the rest.
- Exclude login/admin from all caching layers, then purge cached redirects.
- Add a small redirect regression test to deployments so this becomes a solved problem, not a recurring meeting.