14

Why does this .htaccess work when accessing example.com/mywebsite/

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php

whereas this fails (404):

RewriteEngine On
RewriteRule ^(.*)$ index.php

I thought the second solution should work as well.

MrWhite
  • 43,179
  • 8
  • 60
  • 84
Basj
  • 41,386
  • 99
  • 383
  • 673
  • 4
    The second "solution" will result in an endless rewriting loop. – arkascha Jan 10 '17 at 17:44
  • @arkascha This shouldn't result in a rewrite loop, unless you were using the captured group in the substitution, eg. `index.php/$1`. In fact, if the first worked, the second should also work. – MrWhite Jan 10 '17 at 17:56
  • Do you have another `.htaccess` file in the `/mywebsite` subdirectory? - Assuming that is a real subdirectory? – MrWhite Jan 10 '17 at 17:58
  • @w3dk: no I have no other .htaccess – Basj Jan 10 '17 at 18:00

1 Answers1

29

In order to explain this behaviour, we need to make some assumptions about your file system, and by "work" you mean that a file is served (you don't see a directory listing)...

The .htaccess file is located in the document root and /mywebsite is a physical directory that contains an index.php file (or some DirectoryIndex document). There is no index.php file in the document root. In other words:

example.com/
    .htaccess
    mywebsite/
        index.php

In this scenario, when you request example.com/mywebsite/ the following happens:

RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php

/mywebsite/ is a physical directory, so the first condition fails and the RewriteRule is not processed.

mod_dir then searches for a DirectoryIndex, finds index.php and the .htaccess file is reprocessed. This now maps to a physical file, so the second condition fails and the RewriteRule is not processed.

The net result is that example.com/mywebsite/index.php gets requested. The same as if there was no .htaccess file at all.

RewriteRule ^(.*)$ index.php

However, in this scenario, there are no conditions. The RewriteRule gets processed unconditionally and internally rewrites the request to example.com/index.php (strictly speaking it's <filesystem-path-to-document-root>/index.php) since that is where the .htaccess file is located.

However, there is no index.php file in the document root; hence the 404.

Why is RewriteCond %{REQUEST_FILENAME} !-d mandatory?

Whether it is mandatory or not is really dependent on your filesystem and what you are trying to do. But generally, you don't normally want physical directories to be processed by the front controller.

The !-f condition is usually more important since you often don't want physical files to be processed by the front controller. This is required when you want to serve static resources (eg. CSS, JavaScript and images) from the same area on the filesystem. However, you might omit this directive if you wanted to control access to some physical files (perhaps a "download" section) through the front controller.

MrWhite
  • 43,179
  • 8
  • 60
  • 84
  • 1
    Thanks for this very detailed answer! The only difference is that my .htaccess is inside `mywebsite/`, just next to `index.php`. – Basj Jan 11 '17 at 00:51
  • 1
    In that case, given no other files and no other directives, both should "work". Well, the first rule block won't actually do anything (for the same reasons as mentioned above), you simply end up requesting `index.php` directly (the same as if there was no `.htaccess` file). Without the preceding conditions the request is rewritten regardless, to the same `index.php` file. You shouldn't be getting a 404 here, unless `index.php` itself is triggering it - but that doesn't really make sense, because the requests are pretty much identical. – MrWhite Jan 11 '17 at 22:53