Skip to content
ThemesIonic — home
Troubleshooting

Fixing ERR_TOO_MANY_REDIRECTS on a WordPress Site

A redirect loop is two rules that disagree about the correct URL, each sending the browser to the other's answer. Trace the hops with curl, and the two Location headers tell you which layers are fighting: proxy, server, WordPress or a plugin.

5 min read intermediate

A redirect loop is two layers that disagree about the right URL — find the pair by tracing the hops, then make one of them stop redirecting. The browser gives up after about twenty redirects and shows ERR_TOO_MANY_REDIRECTS, "The page isn't redirecting properly" or "redirected you too many times". The page itself is fine. Something sends http:// to https://, and something else sends it back; or www to the bare domain, and back again.

This article covers loops that hit every visitor. If only logging in loops, and clearing cookies helps, the cause is different and is covered in login redirect loop.

Trace the loop

Do this before changing any setting. curl sends no cookies and follows each hop, so the output is the URL chain your server actually produces:

curl -sIL --max-redirs 6 https://example.com/ | grep -iE '^(HTTP|location|server|x-redirect-by|cf-ray)'

You will see the same two Location values alternating. Three headers tell you who sent each hop:

  • X-Redirect-By: WordPress — WordPress issued it, through wp_redirect(). Plugins that use it can set their own value, and many send their plugin name.
  • No X-Redirect-By and none of your origin's usual headers — probably the proxy or CDN. Behind Cloudflare every response carries cf-ray and Server: cloudflare, so those alone do not show who redirected.
  • No marker with your web server's Server header — an .htaccess, nginx or hosting panel rule.

Then match the pattern:

Hops alternate between Usual pair
http:// and https:// Proxy sends HTTP to origin; server or plugin forces HTTPS
www. and bare domain Server or DNS rule vs WordPress Address
The same https:// URL under /wp-admin/ or /wp-login.php FORCE_SSL_ADMIN behind a proxy
The same front-end URL, every time Cached redirect, or a plugin redirecting to the current page

Pair 1: HTTPS at the proxy, HTTPS rule at the server

The most common loop. A CDN or load balancer accepts HTTPS from the visitor and forwards plain HTTP to your server. Cloudflare's Flexible mode does this, as do many hosting load balancers. The server, or a "force HTTPS" plugin, sees an HTTP request, redirects to HTTPS, and the proxy forwards that as HTTP again.

WordPress makes this worse because of how it detects HTTPS. is_ssl() checks only $_SERVER['HTTPS'] and, failing that, whether the port is 443. It does not read X-Forwarded-Proto or any other proxy header — no part of core does. Behind a TLS-terminating proxy, every request looks insecure to WordPress.

Fix it in two places:

  1. Make the proxy talk HTTPS to the server. In Cloudflare, Full (strict) with an origin certificate; the details are in Cloudflare and WordPress. This removes the loop at its source.
  2. If the proxy must use HTTP, tell PHP the request was secure, at the top of wp-config.php:
if ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && 'https' === $_SERVER['HTTP_X_FORWARDED_PROTO'] ) {
	$_SERVER['HTTPS'] = 'on';
}

Do this only when the server is reachable through the proxy alone; otherwise anyone can send the header. Then remove the server-level HTTP to HTTPS rule, or condition it on the same header, so it no longer fires on proxied requests.

Pair 2: www against the WordPress Address

WordPress has its own canonical redirect. redirect_canonical() rebuilds the requested URL with the host from the Site Address (home) and redirects if the path, port or query differ, or the host differs only by www.. It does not compare the scheme, so it never causes an HTTP/HTTPS loop on the front end — but it does enforce the host.

If Settings › General says https://example.com and the server or DNS provider redirects example.com to www.example.com, the two take turns. Pick one hostname and make every layer agree:

wp option get home
wp option get siteurl
grep -nE "WP_HOME|WP_SITEURL" wp-config.php

Constants in wp-config.php override the database values and grey out the fields in the dashboard, which is why a change in Settings sometimes seems to do nothing. The relationship between the two values is covered in WP_HOME and WP_SITEURL. If you cannot reach the dashboard, edit the constants or the two wp_options rows.

Pair 3: wp-admin only, behind a proxy

When FORCE_SSL_ADMIN is not defined, WordPress defines it as true whenever siteurl starts with https. wp-login.php then redirects any request where is_ssl() is false to the HTTPS version, and auth_redirect() does the same for /wp-admin/. Behind a proxy that forwards HTTP, is_ssl() is always false, so the login screen redirects to itself forever while the front end loads normally.

The fix is the X-Forwarded-Proto snippet above. Defining FORCE_SSL_ADMIN as false also stops the loop, but it leaves admin cookies issued over what WordPress believes is an insecure connection; fix the detection instead.

Pair 4: plugins and cached redirects

  • SSL and redirect plugins add a third opinion. Disable them one at a time by renaming the folder, as described in plugin conflicts, and retest with curl.
  • Page caches and CDNs can store a redirect and serve it after the cause is fixed. A 301 is also cached by the browser. Purge the cache — clearing WordPress cache covers each layer — and test in a private window or with curl.
  • .htaccess rules added by a host or an old plugin sit above the WordPress block. Compare yours with the default .htaccess and remove duplicate HTTPS or www rules.

Common mistakes

  • Changing the site URL, the proxy mode and a plugin at the same time, then not knowing which one fixed it — or broke it next.
  • Testing in a browser that has cached the 301. Use curl.
  • Adding a second HTTPS rule to fix a loop caused by the first.
  • Trusting X-Forwarded-Proto on a server that also accepts direct connections.
  • Leaving http:// in WP_HOME after moving to HTTPS; see setting up SSL.

Verify

  • curl -sIL http://example.com/ ends in one 200 at the canonical https:// URL, after at most two redirects.
  • The same for www. and the bare domain, and for /wp-login.php.
  • wp option get home and siteurl match that final URL exactly.
  • Logged in, wp-admin loads without bouncing, and the front end shows no mixed content.

Frequently asked

Your loop was probably a login or session loop, which lives in cookies. A loop for every visitor is in the URLs themselves: a server, proxy or WordPress rule. Test with curl, which sends no cookies, to tell the two apart.
WordPress forces HTTPS for the login and admin screens whenever the WordPress Address starts with https. Behind a proxy that talks to the server over HTTP, is_ssl() is false, so wp-login.php keeps redirecting to HTTPS, and the proxy keeps delivering HTTP.
It adds one more layer that can disagree with the others. Put the HTTP to HTTPS and www rules in one place, ideally the server or the proxy, and leave WordPress to handle only its own canonical URLs.

Related guides