Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
WordPress Multisite: How to fix error "too many redirects"

WordPress Multisite: How to fix error "Request exceeded the limit of 10 internal redirects"

I am running a WordPress multisite network with sub-directory setup. When I check my error.log file, it is full of entries like this one:

Request exceeded the limit of 10 internal redirects due to probable configuration error. Use 'Limit InternalRecursion' to increase the limit if necessary. Use 'LogLevel debug' to get a backtrace.

The problem was, in my case, one specific rewrite rule in the .htaccess file.

Problem description

If you open the network admin backend an go to the network admin page, then WordPress will suggest you some directives that you should put in your .htaccess file. In my case, these directives look like this:

RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]

# uploaded files
RewriteRule ^([_0-9a-zA-Z-]+/)?files/(.+) wp-includes/ms-files.php?file=$2 [L]

# add a trailing slash to /wp-admin
RewriteRule ^([_0-9a-zA-Z-]+/)?wp-admin$ $1wp-admin/ [R=301,L]

RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
RewriteRule ^([_0-9a-zA-Z-]+/)?(wp-(content|admin|includes).*) $2 [L]
RewriteRule ^([_0-9a-zA-Z-]+/)?(.*\.php)$ $2 [L]
RewriteRule . index.php [L]

Imagine someone requesting a URL like http://example.com/wp-content/file.txt.

If file.txt exists on the server, then Apache will deliver the file to the client because of these directives:

RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]

If, however, file.txt does not exist as a static file on the server, these rules will not fire. But the following rewrite rule will attempt to rewrite the request URI:

RewriteRule ^([_0-9a-zA-Z-]+/)?(wp-(content|admin|includes).*) $2 [L]

But in our example, the requested URL http://example.com/wp-content/file.txt will just be rewritten to itself. Then, Apache tries to process this request again, going through the rules in the .htaccess file again and again. This results in the internal redirect loop.

Solution

What we had to do was to make sure that the aforementioned rewrite rule does not fire anymore for request URIs that begin with /wp-content/. One way to achieve this is to remove the question mark from this directive. So the new rewrite rule would look like this:

RewriteRule ^([_0-9a-zA-Z-]+/)(wp-(content|admin|includes).*) $2 [L]

Another possibility would be to add an additional rewrite condition before this rewrite rule. This would look like this:

RewriteCond %{REQUEST_URI} !^/wp-(content|admin|includes).*$
RewriteRule ^([_0-9a-zA-Z-]+/)?(wp-(content|admin|includes).*) $2 [L]

If you go for this solution, then you might have to adapt the rewrite condition if your RewriteBase is something else than /.

new .htaccess file

I went for the first solution. Here's my new .htaccess file.

RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]

# uploaded files
RewriteRule ^([_0-9a-zA-Z-]+/)?files/(.+) wp-includes/ms-files.php?file=$2 [L]

# add a trailing slash to /wp-admin
RewriteRule ^([_0-9a-zA-Z-]+/)?wp-admin$ $1wp-admin/ [R=301,L]

RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
RewriteRule ^([_0-9a-zA-Z-]+/)(wp-(content|admin|includes).*) $2 [L]
RewriteRule ^([_0-9a-zA-Z-]+/)(.*\.php)$ $2 [L]
RewriteRule . index.php [L]

I haven't had any of these errors since I applied these changes.

Bug report

This bug has been reported to the WordPress developers quite some time ago. I hope there will be a fix soon.

Ok. I messed around with this. I think I have something.

Instead of changing the rewrite rule, Change:

RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]

To:

RewriteCond %{ENV:REDIRECT_STATUS} 200 [OR]
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]

This works by checking internal Apache variable %{ENV:REDIRECT_STATUS}. This variable is empty at the start of rewrite module but is set to 200 when first successful internal rewrite happens. This above condition says bail out of further rewrites after first successful rewrite and stops looping.

Life saver! This one was beyond me.

davidsword commented Mar 30, 2017

I just spent .. well I'm not willing to admit how long .. on trying to solve these massive 404->to->500 loops that were clogging my error log. This solved it and explain it. Shame on me for questioning every single line of my htaccess instead of thinking it could be a Wordpress thing.

Thank you, thank you, thank you!

Totally saved my bacon! Thanks again!!!

Very Helpful!
Thank you very much.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment