Nginx – Rewrite leads to infinite 301 redirect loop on existing directories

apache-2.2mod-rewritenginxrewrite

I went through questions/solutions found here, tried numerous approaches (including the [L] directive) but nothing really did the trick.

Situation Overview

Debian running Apache 2.2 proxying through nginx

Goal

Redirect everything to /index.php and assure a trailing slash, always.

Exclude the following directories from the rule:

  • js_static
  • media

Exclude all .css files from the rule.

The Problem

Apache/nginx lead to a 301 redirect loop when i call www.url.com/js_static. (Problem occurs also with trailing slash – makes no difference)

Current Solution Approach

nginx is configured like this:

gzip_proxied any;
rewrite ^/(.*)/$ /$1;
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:AES256+EECDH:AES256+EDH';

Apache is configured this way:

RewriteEngine On
RewriteCond %{SCRIPT_FILENAME} !^.+\.(css)
RewriteCond %{REQUEST_URI} !^.+js_static
RewriteCond %{REQUEST_URI} !^.+media
RewriteRule ^(.*)$ /index.php/$1
AllowEncodedSlashes On

I fail to see where the problem is. A theory i had was that the combination of nginx/apache rewrites would create the problem, so i fiddled around with the configuration, but to no avail, unfortunately.

Can someone pinpoint the issue here?

Best Answer

tl;dr "The problem" is likely to be caused by mod_dir (Apache) automatically appending the slash when requesting a physical directory. However, disabling mod_dir (ie. DirectorySlash Off) is not necessarily the answer.


Problem occurs also with trailing slash – makes no difference

In your Nginx config (your frontend proxy), you are unconditionally stripping the trailing slash from all URLs (including directories) via an internal rewrite. So, whether you include the trailing slash on the initial request, it will indeed make no difference.

Apache mod_dir (by default) will automatically append a slash when requesting a physical directory (that does not already have a trailing slash) via an external 301 redirect. It does this in order to "fix" the URL. "A directory" is not strictly a valid resource (what would you expect to be returned?). Once "fixed", mod_dir then tries to return the directory index document (eg. index.html) in that directory:

  1. example.com/directory 301 redirect to example.com/directory/
  2. example.com/directory/ internal rewrite to example.com/directory/index.html (or whatever DirectoryIndex document is found). Or a 403 Forbidden if there is no directory index (unless auto-directory indexes are enabled - not recommended.)

However, after the redirect to example.com/directory/, the request hits your Nginx proxy again which strips the trailing slash.... etc. etc. 301 redirect loop.

My personal preference is to always leave the trailing slash on physical directories. However, if you do want to remove the trailing slash from all URLs then you need to disable mod_dir's automatic behaviour and manually append the trailing slash yourself via an internal rewrite (because requesting a bare directory with no trailing slash is not strictly valid in this instance).

Try changing your Apache config to the following

AllowEncodedSlashes On
DirectorySlash Off

RewriteEngine On

# Internally rewrite any directories that do not have a trailing slash
RewriteCond %{REQUEST_FILENAME} -d
RewriteCond %{REQUEST_URI} (.*)
RewriteRule !/$ %1/ [L]

# Internally rewrite all non-static resources to index.php (with path info)
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^/(.*)$ /index.php/$1 [L]

I assume this is in your server config (and not .htaccess)? However, your previous RewriteRule would have resulted in a double slash in the substitution when used in the server config. This should still have resolved correctly, however, it could break some things.

I've also made this more "generic". Rather than specifically checking for .css files and URLs containing js_static or media, this simply checks to make sure the request is not for a physical file. This is far more common (and flexible) as a "front-controller". However, you can change this back if you specifically need to (but there are potential problems if you do).

...redirect loop when I call www.example.com/js_static

Aside: I wouldn't expect this to be a valid request anyway?

Just to echo TeroKilkanen's concerns in the comments. Using both Nginx and Apache for related rewrites is not recommended.

Related Topic