Incident Overview
Minutes after switching a WordPress site to HTTPS, every URL — home page, admin, everything — began failing with ERR_TOO_MANY_REDIRECTS in every browser. The owner reverted the change they thought they’d made; the loop persisted, which is what prompted the emergency request. Total outage, all visitors, all pages.
Redirect loops after SSL migrations are among the most common emergencies we see, and they are almost always the same three-layer disagreement about who terminates TLS.
Raw Diagnostic Logs
Reproduce and count the loop from the command line:
$ curl -sIL -o /dev/null -w "%{num_redirects} redirects, final: %{url_effective}\n" \
--max-redirs 12 https://example-site.com/
curl: (47) Maximum (12) redirects followed
Trace one hop to see who is redirecting to what:
$ curl -sI https://example-site.com/ | grep -iE "^(HTTP|location|server|cf-ray)"
HTTP/2 301
location: https://example-site.com/
server: cloudflare
cf-ray: 8f2ab...
HTTPS redirecting to the same HTTPS URL, forever. That signature means the origin thinks the request is HTTP. Confirmed by hitting the origin directly, bypassing Cloudflare:
$ curl -sI --resolve example-site.com:443:203.0.113.10 https://example-site.com/ \
| grep -iE "^(HTTP|location)"
HTTP/1.1 301 Moved Permanently
Location: https://example-site.com/
Forensic Root Cause Analysis
Three layers each made an individually reasonable decision; together they formed a loop:
- Cloudflare SSL mode was “Flexible”. In Flexible mode, browsers connect to Cloudflare over HTTPS, but Cloudflare fetches from the origin over plain HTTP. So the origin genuinely receives an HTTP request.
- WordPress was configured with
https://site URLs (correct for the migration). Seeing an incoming request it believes is HTTP, WordPress issues a 301 to the HTTPS URL. - The browser follows the redirect to HTTPS → Cloudflare fetches origin via HTTP → WordPress redirects to HTTPS → repeat until
ERR_TOO_MANY_REDIRECTS.
An additional trap found during diagnosis: an old .htaccess force-HTTPS block that keyed off %{HTTPS} off — which is always “off” behind Flexible mode — would have caused the identical loop even if WordPress hadn’t:
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
Root cause: TLS terminated at the proxy with no origin encryption (“Flexible”), while the origin was configured to enforce HTTPS it could never see.
Remediation & Command Log
The correct fix is not to weaken WordPress — it’s to encrypt the Cloudflare→origin leg so every layer agrees. Issue a certificate on the origin and switch Cloudflare to Full (strict):
# Origin certificate via Let's Encrypt (Cloudflare Origin CA is equally valid)
apt-get install -y certbot python3-certbot-apache
certbot --apache -d example-site.com -d www.example-site.com \
--non-interactive --agree-tos -m ops@client-domain.com
Cloudflare dashboard: SSL/TLS mode Flexible → Full (strict).
Make the proxy honest to PHP for any remaining edge cases — trust the forwarded-proto header from Cloudflare:
// wp-config.php — above the "stop editing" line
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
$_SERVER['HTTPS'] = 'on';
}
Replace the naive .htaccess rule with one that respects the forwarded protocol:
RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
Verify — one redirect for HTTP→HTTPS, zero for HTTPS, admin reachable:
$ curl -sIL -o /dev/null -w "%{num_redirects} redirects, final: %{url_effective}\n" \
http://example-site.com/
1 redirects, final: https://example-site.com/
$ curl -sI -o /dev/null -w "%{http_code}\n" https://example-site.com/wp-admin/
302
Preventative Hardening
- HSTS enabled at Cloudflare (with a conservative initial max-age) after confirming the loop-free state for 48 hours — HSTS before a stable config turns a misconfiguration into a lockout.
- Authenticated Origin Pulls enabled so the origin only accepts traffic from Cloudflare, closing the direct-to-origin bypass used during diagnosis.
- Mixed-content sweep: database URLs migrated with
wp search-replace 'http://example-site.com' 'https://example-site.com' --skip-columns=guid, then verified with the browser console clean on the top 10 templates. - The post-repair report documented the TLS topology in one diagram — browser→Cloudflare→origin, with the mode at each hop — so the next person touching DNS or SSL settings can see what “Flexible” would break.
Details in this post-mortem are anonymized and composited from recurring incidents of the same failure class. The logs and commands are representative of the actual diagnostic path. Stuck in a redirect loop right now? Our server error repair service resolves most SSL loops within hours.