Understanding Nginx location directive

Understanding Nginx location directive

nginx location directives

Tools

2020.09.12

0 #nginx #regex

Nginx 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 seem 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 two main blocks used in Nginx configuration file are:

  • server blocks
  • location 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 loaction 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 assoicated 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:

location /site {
    . . .
}

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:

loaction ~* .(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 conifgure 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 longes, 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 loaction 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 encoutered. 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.

Ads by Google

林宏

Frank Lin

Hey, there! This is Frank Lin (@flinhong), one of the 1.4 billion 🇨🇳. This 'inDev. Journal' site holds the exploration of my quirky thoughts and random adventures through life. Hope you enjoy reading and perusing my posts.

YOU MAY ALSO LIKE

Setup an IKEv2 server with StrongSwan

Tutorials

2020.01.09

Setup an IKEv2 server with StrongSwan

IKEv2, or Internet Key Exchange v2, is a protocol that allows for direct IPSec tunneling between the server and client. In IKEv2 implementations, IPSec provides encryption for the network traffic. IKEv2 is natively supported on some platforms (OS X 10.11+, iOS 9.1+, and Windows 10) with no additional applications necessary, and it handles client hiccups quite smoothly.

Using Liquid in Jekyll - Live with Demos

Web Notes

2016.08.20

Using Liquid in Jekyll - Live with Demos

Liquid is a simple templating language that Jekyll uses to process pages on your site. With Liquid you can output an modify variables, have logic statements inside your pages and loop over content.

Practising closures in JavaScript

JavaScript Notes

2018.12.17

Practising closures in JavaScript

JavaScript is a very function-oriented language. As we know, functions are first class objects and can be easily assigned to variables, passed as arguments, returned from another function invocation, or stored into data structures. A function can access variable outside of it. But what happens when an outer variable changes? Does a function get the most recent value or the one that existed when the function was created? Also, what happens when a function invoked in another place - does it get access to the outer variables of the new place?