Apache's mod_rewrite and REQUEST_FILENAME conditions

It can be a little bit tricky (or at least I've not found a clear description on this topic in the Apache docs) to tell where the mod_rewrite module of Apache 2.2.x is going to look for a file if you use a condition. I've had a hard time to get this clear and maybe sharing my experience will benefit others.

It's a common practise (eg. Drupal uses it) to place the following piece of code into a .htaccess file:
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteRule ^(.*)$ index.php?q=$1 [L,QSA]

It works like this: if both conditions (RewriteCond lines) are fulfilled, then the rewrite rule is triggered. The two conditions check that the requested URI does not exist neither as a file, nor as a directory. However how does it really work? Smile If you place the RewriteCond directive in a .htaccess file, then the REQUEST_FILENAME variable will contain the concatenation of the full path of the directory (where the .htaccess is located) and the requested URI.
So if the .htaccess is in a directory like "/var/www/example/dir", the DocumentRoot is "/var/www/example" and the requested URI is "http://www.example.com/dir/somedir/somefile", then REQUEST_FILENAME will take the value of "/var/www/example/dir/somedir/somefile".

But what happens if you place the same rule into the server config or a virtual host config? The answer is: it depends. Smile To me it appeared fairly logical that the base dir for REQUEST_FILENAME will be the DocumentRoot of the given virtualhost, but it seems mod_rewrite developers thought otherwise (and I must admit they're right, but imho having this in the official documentation would spare some hours of scratching heads for most people). If you place the RewriteCond directive in the root section of the virtualhost config, then nothing will be prepended to the requested URI. Using the previous example (the DocumentRoot is "/var/www/example" and the requested URI is "http://www.example.com/dir/somedir/somefile") the value of REQUEST_FILENAME will be "/somedir/somefile"! Shock

However if you place the RewriteCond inside a <Directory> directive, it'll suddenly start working as if it was in a .htaccess file (the path of the given directory will be prepended to the requested URI)! Thus if you want to move a mod_rewrite rule from a .htaccess file to a virtual host config and it relies on conditions on the REQUEST_FILENAME variable, then you should place the rewrite rule in a <Directory> container so you won't have to prepend the base directory in the "RewriteCond %{REQUEST_FILENAME} !-f" expression.

If for some reason you don't want to do this, you can still include the DocumentRoot in the rewrite condition like this:
  RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} !-f
  RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} !-d
  RewriteRule ^(.*)$ /index.php?q=$1 [L,QSA]

Note the small difference in the RewriteRule too: you've to use absolute pathes now, which in our case means adding a slash (/) before the index.php URI.

P.S.: using the RewriteBase directive in a .htaccess file complicates things a little bit further, but it's fairly easy to follow and understand so I'm not going into details on that.

P.S.2: after having gone through hours of debugging, I've found a Drupal specific page that deals with this mod_rewrite configuration madness and has the same solution that I came up with. You might want to read it since it contains a lot of insightful comments from fellow visitors. I've added some clarification in a comment regarding the issue with broken pages (missing style sheets, etc.) in case no $base_url is provided in settings.php.

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Thank you, sir!

This helped me fix up an annoying problem I was having with fixing up trailing slashes on directories. I couldn't figure out why my simple rule wasn't working! Even when I cut-n-pasted code from the Apache guide, it didn't work.

The problem was precisely what you describe here, so thanks for the tip.