Having this simple nginx configuration:
location / {
return 200 "Location 1\n";
}
location ~ \.php$ {
return 200 "Location 2\n";
}
location /tmp {
return 200 "Location 3\n";
location ~ \.php$ {
return 200 "Location 3a\n";
}
}
Why the /tmp/foo.php
requests gives the Location 3a
response, though according the documentation, the regex location Location 2
should overtake the request unless the prefix location Location 3
has the ^~
modifier?
This happens because the actual algorithm for selecting a location in nginx differs from what's described in the documentation. Or, to be more specific, the official documentation does not explain the location selection process for nested locations, which is considerably more complex. So far, I haven't come across any English-language articles explaining how it truly works, so here is my attempt to clarify it.
Let's start with the configuration provided in the original question:
One can think the request
/tmp/foo.php
will be proceed with thelocation ~ \.php$ { ... }
(marked as "Location 2") since the documentation explicitly says:Let's check it:
Unexpected, isn't it? What documentation doesn't say is that after identifying the longest matching prefix location, nginx begins a new search iteration over its nested locations, if any. This process continues recursively: at each step, the same matching rules are applied, and if a matching nested regex location is found, while no nested longest prefix location is matched, nginx stops further searching and uses that location for handling the request.
In our example, for the
/tmp/foo.php
request:location /tmp { ... }
(marked as "Location 3").location ~ \.php$ { ... }
(marked as "Location 3a") matches and is used for handling the request.Now, let's extend the configuration by adding a fourth location:
Testing the same request:
What happened here?
location /tmp/foo { ... }
.location ~ \.php$ { ... }
matches and is used for handling the request.Other requests like
/tmp/bar.php
continue to be processed as before:Next, let's modify the configuration again, moving the
location /tmp/foo { ... }
inside thelocation /tmp { ... }
:Running some tests:
Nothing unexpected so far.
Now, let's examine the
^~
location directive modifier. According to documentation:Let's check it using the following configuration:
Running some tests:
Seems that everything works as expected. The
^~
modifier onlocation ^~ /tmp/foo { ... }
ensures that the same level regex locations, such aslocation ~ \.php$ { ... }
, cannot overtake matching requests like/tmp/foo.php
.Now, here's something that may really surprise you. Let's modify our configuration, moving the
location ^~ /tmp/foo { ... }
inside thelocation /tmp { ... }
again:Let's test the same
/tmp/foo.php
request. You're expecting the response will beLocation 3b
, right?Oops... Looks weird, didn't it? Well, this is probably the last aspect of the algorithm that needs to be explained. After identifying the longest matching prefix location at the most nested level (in our case, the
location ^~ /tmp/foo { ... }
, marked as "Location 3b"), if no matching regex locations are found within it, nginx remembers that location and begins ascending the configuration tree back up to the outermost level. On each level it passes during the ascent, the following actions are taken:^~
modifier, nginx matches the request against all regex locations defined at that level. The first matching regex location, if any, is used to handle the request; otherwise, nginx continues ascending.^~
modifier, nginx skips matching the request against regex locations defined on that level and immediately ascends to the next level up. If it has already reached the outermost level, the remembered longest matching prefix location from the most nested level is used to handle the request.That's it. Having the
^~
modifier on a nested prefix location (location 3b) doesn't guarantee that the request will not be overtaken by an outer regex location (location 2) if the outermost longest matching prefix location (location 3) doesn't have^~
modifier.P.S. Of course, it should be mentioned that if an exact match location (a location with the
=
modifier) is found at any level during the descent, nginx immediately stops searching, and the found location is used to handle the request. Considering it, a typical nginx configuration likecan be significately optimized if the
index.php
file is the sole handler for the requests, which is common in many use cases: