How to Fix ERR_TOO_MANY_REDIRECTS
ERR_TOO_MANY_REDIRECTS is the message Chrome displays when a request bounces from one URL to another so many times that the browser gives up to protect itself from an infinite loop. Behind the scenes, the server keeps issuing redirect responses (301, 302, 307, or 308) that point at each other, and the chain never resolves to an actual page. The browser typically halts after about 20 hops and shows the familiar "This page isn't redirecting properly" screen.
Unlike a 502 or 504, the server here is perfectly healthy — it is just misconfigured. A redirect that should fire once is firing on every request, including the request it redirects to. The good news is that the loop almost always traces back to one of six root causes, and each one has a definitive fix. This guide walks through all of them, in the order you are most likely to encounter them, for Nginx, Apache, WordPress, and Cloudflare.
What Causes Redirect Loops?
A redirect loop is always a disagreement about where the canonical version of a URL lives. Two redirects argue with each other, and the browser is caught in the middle. The two arguments that cause the vast majority of loops are the scheme (HTTP vs HTTPS) and the host (www vs non-www).
HTTP → HTTPS loops
The most common modern loop happens between a CDN and an origin that both insist on HTTPS. Your origin server is configured to redirect every HTTP request to HTTPS — which is correct in isolation. But a proxy like Cloudflare in "Flexible" SSL mode connects to your origin over plain HTTP on every request, even when the visitor used HTTPS. So the visitor hits HTTPS, Cloudflare asks the origin over HTTP, the origin says "go to HTTPS," Cloudflare asks again over HTTP, and the cycle repeats forever. The origin is doing the right thing; the problem is that it cannot see the real scheme the visitor used.
www ↔ non-www loops
The second classic loop is a host mismatch. Your DNS, your web server, your CMS settings, and your CDN each have an opinion about whether example.com or www.example.com is canonical. If the server redirects www to non-www but the CMS rewrites every non-www URL back to www, you get a loop. The same thing happens when two server blocks each redirect to the other, or when a redirect rule forgets to exclude the target it is redirecting to.
Common Causes
Before diving into the step-by-step fix, use this table to quickly match your situation to its root cause and the section that resolves it.
| Cause | Where It Happens | Fix |
|---|---|---|
| Cloudflare Flexible SSL redirects HTTP to HTTPS at the origin | Cloudflare + origin forcing HTTPS | Set Cloudflare SSL mode to Full or Full (Strict) (Step 2) |
| www and non-www both redirect to each other | Nginx/Apache server blocks, CMS settings | Pick one canonical host; redirect the other once (Step 3) |
Two conflicting return 301 / RewriteRule directives |
Nginx.conf, Apache .htaccess | Trace with curl -IL; remove the conflicting rule (Step 4) |
WordPress siteurl / home point to the wrong scheme or host |
wp_options table |
Correct the options or hardcode in wp-config.php (Step 5) |
| Force-SSL or caching plugin adds a second redirect layer | WordPress plugins (e.g. Really Simple SSL) | Disable the plugin; let the server handle HTTPS (Step 6) |
| Stale cookies pin the browser to an old variant | Client browser | Clear cookies for the domain; test in incognito (Step 1) |
Step-by-Step Fix Guide
Step 1: Clear browser cookies and cache
Before changing anything on the server, rule out the client. Browsers cache per-domain cookies that record which scheme or host variant you last used, and HSTS can pin a site to HTTPS for months. A stale cookie can make it look like the whole site is broken when only your browser is looping.
In Chrome, open DevTools (F12), go to Application → Cookies, select the domain, and delete every entry. Then test the URL in a fresh incognito window. If the loop disappears, the cause was a cached redirect or cookie on your machine — no server change is needed, although you may still want to find what set the bad cookie so other visitors are not affected.
Step 2: Check HTTPS/SSL configuration (Cloudflare Flexible SSL issue)
If the site works from the server but loops through a CDN, Flexible SSL is the prime suspect. In Flexible mode Cloudflare terminates the visitor's HTTPS connection and talks to your origin over HTTP. If your origin forces HTTP→HTTPS, every request loops.
The cleanest fix is to install a valid certificate on the origin and switch Cloudflare's SSL/TLS encryption mode to Full or Full (Strict). Now Cloudflare connects over HTTPS, the origin sees a real HTTPS request, and no redirect fires. If you cannot install an origin certificate immediately, you can instead make the origin trust the X-Forwarded-Proto header that Cloudflare sends — see the Nginx configuration in the next section.
Step 3: Verify www vs non-www consistency
Choose one canonical host and enforce it in exactly one place. Decide whether https://example.com or https://www.example.com is canonical, then make sure that DNS, the web server, the CMS, and the CDN all agree. The most common mistake is redirecting at the server and forcing the opposite host inside the CMS, so the two fight each other.
Trace the chain to see exactly what each layer does:
# Follow redirects for both hosts and watch where they go
curl -ILk https://example.com
curl -ILk https://www.example.com
Each host should resolve to your canonical URL with a single 301 and then a 200. If www redirects to non-www and non-www redirects back to www, you have found the loop. Remove the redirect on the canonical side so it serves content directly.
Step 4: Check Nginx/Apache redirect rules
Conflicting redirect directives are the next culprit. In Nginx, look for multiple return 301 statements that target each other, or a redirect inside the destination server block. In Apache, look at .htaccess for RewriteRule chains where one rule's target matches another rule's condition.
# Trace the full redirect chain with headers
curl -ILk https://example.com/
Read each location: header in order. The moment you see a URL repeat, you have found the two rules that are arguing. Comment out one of them, reload the server, and test again. A correct setup has exactly one redirect to the canonical scheme and host, and the canonical server block serves a 200 with no further redirect.
Step 5: Fix WordPress siteurl and home options in wp_options
WordPress reads two options — siteurl and home — from the wp_options table to know its own address. If either contains the wrong scheme (e.g. http:// when you serve HTTPS) or the wrong host (e.g. www when your server redirects www away), WordPress generates links that conflict with the server and the site loops — often locking you out of the admin dashboard.
You can correct the values directly in the database, but the fastest and most reliable fix is to hardcode both constants in wp-config.php. This overrides the database values entirely and stops WordPress from second-guessing the server. The exact code is shown in the WordPress section below.
Step 6: Disable plugins and check .htaccess
If the server and CMS settings look correct but the loop persists, a plugin is almost always adding a second redirect layer. Force-SSL plugins (such as Really Simple SSL), caching plugins, and SEO plugins can each insert their own HTTP→HTTPS or www rewrite on top of what the server already does. Two redirects doing the same job is enough to create a loop.
To test, temporarily rename the wp-content/plugins folder via FTP/SSH (this deactivates all plugins at once). If the loop clears, reactivate plugins one by one until it returns — the last one enabled is the culprit. Also regenerate a clean .htaccess by saving Settings → Permalinks in WordPress, which overwrites any corrupted rewrite rules.
Nginx Redirect Configuration
A correct Nginx setup redirects HTTP to HTTPS exactly once, redirects the non-canonical host to the canonical host exactly once, and serves the final page with no further redirect. The key when sitting behind Cloudflare or any reverse proxy is to trust the X-Forwarded-Proto header so the origin only redirects when the visitor really arrived over HTTP.
# Redirect HTTP -> HTTPS (and www -> non-www) in one hop
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
# Redirect www HTTPS -> non-www HTTPS
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name www.example.com;
# ssl_certificate / ssl_certificate_key here
return 301 https://example.com$request_uri;
}
# Canonical server block - NO redirect here
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name example.com;
# ssl_certificate / ssl_certificate_key here
# Behind Cloudflare: trust the real visitor scheme.
# Only redirect when the visitor actually used HTTP.
if ($http_x_forwarded_proto = "http") {
return 301 https://$host$request_uri;
}
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
The critical detail is the if ($http_x_forwarded_proto = "http") check. Without it, a Flexible-SSL Cloudflare request (which arrives over HTTP at the origin) would be redirected to HTTPS, sent back to Cloudflare, and re-fetched over HTTP — the classic loop. With it, the origin only redirects when the visitor truly used HTTP, which never happens behind a correctly configured proxy.
After editing, test and reload:
nginx -t # validate syntax
nginx -s reload # apply without dropping connections
WordPress wp-config.php Fix
When a WordPress redirect loop locks you out of /wp-admin, the fastest recovery is to hardcode the site URL in wp-config.php. Add these two lines above the /* That's all, stop editing! */ comment, using your canonical https URL:
define('WP_HOME', 'https://example.com');
define('WP_SITEURL', 'https://example.com');
These constants override the home and siteurl rows in the wp_options table, so even if the database still holds a wrong http:// or www value, WordPress will use the correct URL and the loop stops. Once you can log in again, also fix the values in the database so they match:
UPDATE wp_options
SET option_value = 'https://example.com'
WHERE option_name IN ('siteurl', 'home');
Keep the define() lines in wp-config.php if you want to prevent anyone from changing the URL through the admin UI in future; otherwise you can remove them after correcting the database.
Quick Reference Table
Redirect loops can involve any of the four redirect status codes. Understanding the difference helps you read a curl -IL trace and choose the right code for permanent vs temporary moves.
| Code | Name | Permanent? | Method Preserved? | Typical Use |
|---|---|---|---|---|
| 301 | Moved Permanently | Yes | No (may change POST to GET) | Permanent URL change, HTTP→HTTPS |
| 302 | Found | No | No (may change POST to GET) | Temporary redirect |
| 307 | Temporary Redirect | No | Yes (method and body kept) | Temporary redirect that must keep POST |
| 308 | Permanent Redirect | Yes | Yes (method and body kept) | Permanent move that must keep POST |
Note that a loop is not caused by using the "wrong" code — any of 301/302/307/308 can loop if two redirects target each other. But 301 is cached aggressively by browsers, so a loop caused by a 301 can persist even after you fix the server, until you clear the cached redirect. That is why Step 1 (clearing cookies and cache) matters even when the root cause is server-side.
FAQ
How do I clear cookies for just one site in Chrome?
Open DevTools (F12) and go to Application → Storage → Clear storage, or expand Cookies and delete the entries for that domain. Alternatively, click the padlock icon next to the URL, choose Cookies and site data, and remove them. Then reload in an incognito window to confirm the loop is gone before assuming the server is at fault.
Why does ERR_TOO_MANY_REDIRECTS only happen behind Cloudflare?
Because Cloudflare sits between the visitor and your origin and can change the scheme the origin sees. In Flexible SSL mode, Cloudflare connects to the origin over HTTP even though the visitor used HTTPS. If your origin forces HTTP→HTTPS, it redirects a request that Cloudflare will always re-issue over HTTP, creating an endless loop. Switching to Full or Full (Strict) SSL mode, or trusting the X-Forwarded-Proto header on the origin, resolves it.
Can a bad .htaccess file cause a redirect loop?
Yes. A common Apache loop is a RewriteRule that redirects HTTP to HTTPS without checking whether the request is already HTTPS, or two rules where one redirects www to non-www and the other does the reverse. Rename .htaccess to test: if the loop stops, the file is the cause. Regenerate a clean one by resaving Permalinks in WordPress or by restoring your backup.
How do I fix a WordPress redirect loop when I cannot access wp-admin?
Edit wp-config.php over FTP or SSH and add define('WP_HOME','https://example.com'); and define('WP_SITEURL','https://example.com'); with your canonical URL. This overrides the database values that are causing the loop and usually restores access immediately. You can also rename the wp-content/plugins folder to rule out a plugin, then log in and fix the siteurl/home rows in wp_options.
Conclusion
ERR_TOO_MANY_REDIRECTS is never random — it is always two redirects arguing about the canonical scheme or host. Work through the six steps in order: clear client cookies, fix Cloudflare's SSL mode, agree on a single www/non-www host, remove conflicting Nginx/Apache rules, correct WordPress siteurl/home, and disable force-SSL plugins. Use curl -IL to watch the chain and stop the moment a URL repeats. In almost every case the loop is resolved within a single configuration change, and once your canonical server block serves a clean 200, the error is gone for good.