← All case studies

Post-Mortem: ERR_TOO_MANY_REDIRECTS After an SSL Migration

Environment
Cloudflare (proxy) · Ubuntu 22.04 · Apache 2.4 · PHP 8.2 · WordPress
Symptom
ERR_TOO_MANY_REDIRECTS on every page for every visitor
Resolution
40 minutes from access to resolution; hardening same day
Published

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:

  1. 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.
  2. 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.
  3. 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.

Facing the same failure right now?

This is the exact process a repair follows — start with the matching recovery service.

Fix My Site Now