How to fix "too many redirects" on WordPress behind Cloudflare or a proxy
The browser says "This page isn't working: redirected you too many times". Two parts of your setup each send the visitor somewhere else, and they never agree. This guide shows how to see the loop and which part to change.
1. See the loop
Don't guess from the browser. It also remembers old redirects. This command shows each step, one by one:
curl -sI https://example.com/ | grep -iE "^(HTTP|location|x-redirect-by)"
Then run it again on the address in the location line. In a loop, you'll soon be back where you started. The x-redirect-by: WordPress line tells you WordPress sent that redirect. Without it, it usually came from Nginx, Apache or Cloudflare.
To see the whole chain at once:
curl -sIL --max-redirs 10 https://example.com/ | grep -iE "^(HTTP|location)"
2. The most common cause: Cloudflare "Flexible" SSL
With Cloudflare's Flexible mode, visitors use HTTPS to reach Cloudflare, but Cloudflare talks to your server over plain HTTP. If your server sends HTTP visitors to HTTPS (as it should), this happens:
- The visitor asks Cloudflare for
https://example.com/. - Cloudflare asks your server for
http://example.com/. - Your server answers "go to
https://example.com/". - The visitor goes there, and it starts again.
In our test, curl gave up after 10 redirects, each one 301 to the same https://example.com/, without x-redirect-by.
The fix: switch Cloudflare to "Full (strict)". In the Cloudflare dashboard, open your domain's SSL/TLS settings and set the encryption mode to Full (strict). Now Cloudflare talks to your server over HTTPS too. In our test, the same site loaded with no loop as soon as the proxy used HTTPS.
Full (strict) needs a valid certificate on your server. A free Let's Encrypt certificate works: see HTTPS with Let's Encrypt. Cloudflare's free Origin CA certificate also works, but only behind Cloudflare. Avoid "Full" without "strict": it accepts any certificate, so it doesn't check that it's really talking to your server.
Flexible also has a security problem: the part between Cloudflare and your server isn't encrypted. Your visitors see a lock, but logins and forms travel in plain text on that part. So Full (strict) is the right fix, not just a way around the loop.
3. You removed the redirect, and only wp-admin still loops
Some people "fix" the Flexible loop by removing the HTTP-to-HTTPS redirect on the server. We tried that too. The home page loaded, but:
/wp-admin/andwp-login.phpstill looped, now withx-redirect-by: WordPress. WordPress saw a plain HTTP request and kept sending the visitor to HTTPS.- The page loaded its CSS files from
http://addresses, which browsers block on an HTTPS page ("mixed content").
WordPress decides "is this HTTPS?" by looking at the connection to your server. Behind a proxy that talks plain HTTP, the answer is always no. If you really can't use Full (strict), tell WordPress to trust the header the proxy sends. Add this to wp-config.php, above /* That's all, stop editing! */:
if ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https' ) {
$_SERVER['HTTPS'] = 'on';
}
In our test, this stopped the wp-admin loop and the http:// links. But anyone who can reach your server directly can send that header too. In our test, a plain HTTP request straight to the server with a fake header was treated as HTTPS. So only use it if your server accepts web traffic from Cloudflare only. Other proxies and load balancers send the same header, so this works behind them too.
4. www and non-www disagree
The other classic loop: the web server sends www.example.com to example.com, but WordPress thinks its address is www.example.com and sends visitors back. In our test, the two steps looked like this:
https://example.com/ → 301 → https://www.example.com/ (x-redirect-by: WordPress)
https://www.example.com/ → 301 → https://example.com/ (from Nginx)
Pick one address and use it everywhere. Check what WordPress thinks:
sudo mysql wordpress -e "SELECT option_name, option_value FROM wp_options WHERE option_name IN ('home','siteurl')"
Change wordpress to your database name, and wp_ if your tables use another prefix. In our test, setting both to the same address as the Nginx redirect ended the loop. You can't reach the dashboard while it loops, so you have two ways to fix it:
- In
wp-config.php(easiest; it wins over the database):
In our test, this fixed the loop at once, even with the wrong address still in the database.define( 'WP_HOME', 'https://example.com' ); define( 'WP_SITEURL', 'https://example.com' ); - In the database:
sudo mysql wordpress -e "UPDATE wp_options SET option_value='https://example.com' WHERE option_name IN ('home','siteurl')"
Also look at Cloudflare's Rules → Redirect Rules and Page Rules. A www rule there can fight with your server or WordPress in the same way.
5. Still looping? Check these
- Old redirects in the browser. Browsers remember
301redirects. Test in a private window or with curl, which remembers nothing. - Redirect plugins. HTTPS and redirect plugins add their own rules. Turn them off by renaming the plugin's folder in
wp-content/plugins, then test again. - Cache. A page cache (in a plugin, Nginx or Cloudflare) can keep serving an old redirect. Clear it after each change.
- A redirect in two places. An HTTPS redirect both in Nginx and in Cloudflare ("Always Use HTTPS") is fine on its own. It loops only if one side talks HTTP to the other, as in step 2.
Quick list
- 301 to the same https:// address, again and again: Cloudflare is on Flexible. Switch to Full (strict) and put a real certificate on the server.
- Only wp-admin loops, with x-redirect-by: WordPress: WordPress doesn't know it's behind HTTPS. Use Full (strict), or add the
X-Forwarded-Protocheck towp-config.php. - www ↔ non-www back and forth: make WordPress's
homeandsiteurlmatch the server's redirect, or setWP_HOMEandWP_SITEURL. - Works in curl but not in the browser: clear the browser's cache or use a private window.
Official sources
- Cloudflare: encryption modes (Flexible, Full, Full strict)
- Cloudflare: ERR_TOO_MANY_REDIRECTS
- HTTPS for WordPress (WordPress.org), including sites behind a proxy
- Changing the site URL (WordPress.org)
Next steps: lock your server to Cloudflare so nobody can go around it, and add security headers now that HTTPS works.
ServerNest is being built to set up HTTPS for each site and check that it keeps working. Join early access to try it first.