Location directives are essential when working with Nginx. They can be located within server blocks or other location blocks. Understanding how location directives are used to process the URI of client request can help make the request handling less unpredictable.
Nginx block configurations
Nginx logically divides the configurations meant to serve different content into blocks, which live in a hierarchical structure. Each time a client request is made, Nginx begins a process of determining which configuration blocks should be used to handle the request.
The main two blocks used in Nginx configuration file are:
server
blockslocation
blocks
A server
block consists of a subset of configurations that define a virtual server. Multiple server blocks are possible to decide which block will handle the request based on domain name, IP, address and port. A location
block, on the other hand, is located within a server block and defines how request are processed for different URIs and resources. The URI space can be separated in pretty much any location
block. It is an extremely flexible model.
Matching location blocks
Location block syntax
Below is the general structure of an Nginx location
block:
location optional_modifier location_match {
. . .
}
The location_match
defines what Nginx should check the request URI against. The existence or non-existence of the modifier in the above example affects the way that the Nginx attempts to match the location block. The modifiers below will cause the associated location block to be interpreted as follows:
(none): If no modifier are present, the location is interpreted as a prefix match. This means that the location given will be matched against the beginning of the request URI to determine a match.
=
: If an equal sign is used, this block will be considered a match if the request URI exactly matches the location given.
~
: If a tilde modifier is present, this location will be interpreted as a case-sensitive regular expression match.
~*
: If a tilde and asterisk modifier is used, the location block will be interpreted as as case-insensitive regular expression match.
^~
: If a caret and tilde modifier is present, and if this block is selected as the best non-regular express match, regular expression matching will not take place.
Examples of location block syntax
As an example of prefix matching, the following location block may be selected to respond for request URIs that look like /site
, /site/path1/index.html
, or /site/index.html
:
For a demonstration of exact request URI matching, this block will always be used to respond to a request URI that looks like /path1
. It will NOT be used to respond to a path1/index.html
request URI. Keep in mind that if this block is selected and the request if fulfilled using an index page, an internal redirect will take place to another location that will be the actual handler of the request:
location = /path1 {
. . .
}
As an example of a location that should be interpreted as a case-sensitive regular expression, this block cloud be used to handle request for /rose.jpg
but NOT for /ROSE.PNG
:
location ~ .(jpe?g|png|gif|ico|svg)$ {
. . .
}
A block that would allow for case-insensitive matching similar to the above is shown below. Here, both rose.jpg
and ROSE.PNG
could be handled by this block:
location ~* .(jpe?g|png|gif|ico|svg)$ {
. . .
}
Following block would prevent regular expression matching from occurring if it is determined to be the best non-regular expression match. It could handle requests for costumes/ninja.html
:
location ^~ /costumes {
. . .
}
The process of choosing Nginx location blocks
Nginx runs through a process that determines the best location block for any given request. Understanding this process is a crucial requirement in being able to configure Nginx reliably and accurately.
Nginx evaluates the possible location contexts by comparing the request URI to each of the locations. It does this using the following algorithm:
Nginx begins by checking all prefix-based location matches (all location types not involving a regular expression). It checks each location against the complete request URI.
Nginx looks for an exact match. If a location block using the =
modifier is found to match the request URI exactly, this location block is immediately selected to serve the request.
If no exact location block matches are found, Nginx then moves on to evaluating non-exact prefixes. It discovers the longest matching prefix location for the given request URI, which it then evaluates as follows:
If the longest matching prefix location has the ^~
modifier, then Nginx will immediately end its search and select this location to serve the request.
If the longest matching prefix location does not use the ^~
modifier, the match is stored by Nginx for the moment so that the focus of the search can be shifted.
After the longest matching prefix location is determined and stored, Nginx moves on to evaluating the regular expression locations (both case sensitive and insensitive). If there are any regular expression location within the longest matching prefix location, Nginx will move those to the top of its list of regex locations to check. Nginx then tries to match against the regular expression locations sequentially. The first regular expression location that matches the request URI is immediately selected to serve the request.
If no regular expression locations are found that match the request URI, the previously stored prefix location is selected to serve the request.
It is important to understand that, by default, Nginx will serve regular expression matches in preference to prefix matches. However, it evaluates prefix locations first, allowing for later override this tendency by specifying locations using the =
and ^~
modifiers.
It is also important to note that, while prefix locations generally select based on the longest, most specific match, regular expression evaluation is topped when the first matching location is found. This means that positioning within the configuration has vast implications for regular expression locations.
Location block evaluation jump to other location
Generally speaking, when a location block is selected to serve a request, the request is handled entirely within that context from that point onward. Only the selected location and the inherited directives determine how the request is processed, without interference from sibling location blocks.
Although this is a general rule that will allow you to design your location blocks in a predictable way, it is important to realise that there are times when a new location search is triggered by certain directives within the selected location. The exceptions to the “only one location block” rule may have implications on how the request is actually served and may not align with the expectations you had when designing your location blocks.
Some directives that can lead to this type of internal redirect are:
index
try_files
rewrite
error_page
The index
directive always leads to an internal redirect if is is used to handle the request. Exact location matches are often used to speed up the selection process by immediately ending the execution of the search algorithm. However, if you make an exact location match that is a directory, there is a good chance that the request will be redirected to a different location for actual processing.
In this example, the first location is matched by a request URI of /exact
, but in order to handle the request, the index
directive inherited by the block initiates an internal redirect to the second block:
index index.html;
location = /exact {
. . .
}
location / {
. . .
}
In the example above, if you really need the execution to stay in the first block, you will have to come up with a different method of satisfying the request to the directory. For instance, you could set an invalid index
for that block and turn on autoindex
:
location = /exact {
index nothing_will_match;
autoindex on;
}
location / {
. . .
}
This is one way of preventing an index
from switching contexts, but it’s probably not useful for most configurations. Mostly an exact match on directories can be helpful for things like rewriting the request (which also results in a new location search).
Another instance where the processing location may be re-evaluated is with the try_files
directive. This directive tells Nginx to check for the existence of a named set of files or directories. The last parameter can be a URI that Nginx will make an internal redirect to.
Consider the following configuration:
root /var/www/dist;
location / {
try_files $uri $uri.html $uri/ /fallback/index.html;
}
location /fallback {
root /var/www/another;
}
In the above example, if a request is made for /blahblah
, the first location will initially get the request. It will try to find a file called blahblah
in /var/www/dist
directory. If it cannot find one, it will follow up by searching for a file called blahblah.html
. It will then try to see if there is directory called blahblah/
within the /var/www/dist
directory. Failing all of these attempts, it will redirect to /fallback/index.html
. This will trigger another location search that will be caught by the second location block.
Another directive that can lead to a location block pass off is the rewrite
directive. When using the last
parameter with the rewrite
directive, or when using no parameter at all, Nginx will search for a new matching location based on the results of the rewrite.
For example, if we modify the last example to include a rewrite, we can see that the request is sometimes passed directly to the second location without relying on the try_files
directive:
root /var/www/dist;
location / {
rewrite ^/rewriteme/(.*)$ /$1 last;
try_files $uri $uri.html $uri/ /fallback/index.html;
}
location /fallback {
root /var/www/another;
}
In the above example, a request for /rewriteme/hello
will be handled initially by the first location block. It will be rewritten to /hello
and a location will be searched. In this case, it will match the first location again and be processed by the try_files
as usual, maybe kicking back to /fallback/index.html
if nothing is found.
However, if request is made for /rewriteme/fallback/hello
, the first block again will match. The rewrite be applied again, this time resulting in /fallback/hello
. The request will then be served out of the second location block.
A related situation happens with the return
directive when sending the 301
or 302
status codes. The difference in this case is that it results in an entirely new request in the form of an externally visible redirect. This same situation can occur with the rewrite
directive when using the redirect
or permanent
flags. However, these location searches shouldn’t be unexpected, since externally visible redirects always result in a new request.
The error_page
directive can lead to an internal redirect similar to that created by try_files
. This directive is used to define what should happen when certain status codes are encountered. This will likely never be executed if try_files
is set, since that directive handles the entire life cycle of a request.
Consider this example:
root /var/www/dist;
location / {
error_page 404 /error/whoops.html;
}
location /error {
root /var/www;
}
Every request (other than those starting with /error
) will be handled by the first block, which will serve files out of /var/www/dist
. However, if a file is not found (a 404
status), an internal redirect to /error/whoops.html
will occur, leading to a new location search that will eventually land on the second block.
As you can see, understanding the circumstances in which Nginx triggers a new location search can help to predict the behaviour you will see when making requests.
Conclusion
Understanding the way that Nginx selects different location blocks will give you the ability to trace the contexts that Nginx will apply in order to serve each request.