You get a bonus - 1 coin for daily activity. Now you have 1 coin

Algorithms for Selecting Server and Location Blocks in Nginx

Lecture



Nginx – is one of the most popular web servers in the world. It can handle high loads and a large number of simultaneous connections. Nginx can also be used as a load balancer, a mail server, or a reverse proxy.

This tutorial explains how Nginx processes client requests. Understanding this mechanism will help you optimize request processing.

Nginx block configuration

Nginx logically divides configurations intended for serving different content into blocks, which are assembled into a hierarchical structure. Nginx begins processing each client request by determining the required configuration blocks. This decision-making process will be the central topic of this tutorial.

The main blocks we will discuss are called server and location.

A server block – is a subset of the Nginx configuration that defines a virtual server used to handle requests of a certain type. Administrators often configure several server blocks, where each block handles connections based on the requested domain, port, and IP address.

A location block is located inside a server block and is used so that Nginx can process requests for different resources and URIs of the parent server. With this block, an administrator can partition the URI space in the required way. This is an extremely flexible model.

1: Finding the server block

Nginx allows you to define several server blocks that function as separate virtual web server instances. Because of this, Nginx needs a procedure for determining which of these blocks will be used to process a request.

To do this, Nginx applies a specific set of checks used to find the best match. The main server block directives that help Nginx determine the required block are listen and server_name.

The listen directive

First, Nginx looks at the IP address and port of the request. It matches these values against the listen directive of each server block and creates a list of blocks that can serve the request.

The listen directive usually defines the IP address and port of the server block. By default, any server block that has no listen directive receives the parameters 0.0.0.0:80 (or 0.0.0.0:8080, if Nginx is run by a regular user without root privileges). This allows such blocks to respond to requests on any interface on port 80. But this default value does not carry much weight in the block selection process.

The listen directive can specify:

  • An IP address and port.
  • Only an IP address (in which case the default port 80 will be used).
  • Only a port (in which case all interfaces will be listened on).
  • A path to a Unix socket.

The last option is generally only used when passing requests between different servers.

First, Nginx will try to select a block based on the listen directive, using the following rules:

  • Nginx translates all «incomplete» listen directives, replacing missing values with default values, in order to then evaluate each block by its IP address and port. For example:
    • If a block has no listen directive, the block will be assigned the value 0.0.0.0:80.
    • If a block specifies only the IP address 111.111.111.111, it will be assigned the default port: 111.111.111.111:80.
    • If a block specifies only the port 8888, it will be assigned the default IP address: 0.0.0.0:8888.
  • Then Nginx tries to build a list of server blocks that match the request, based specifically on the IP address and port. This means that any block using the IP address 0.0.0.0 will not be selected if there are blocks configured for the given IP address. The port must match exactly.
  • If the web server finds exactly one match, it simply uses that server block to serve the request. If it finds several blocks that satisfy all requirements, Nginx will select one block based on the server_name directive.

It is important to understand that Nginx will evaluate the server_name directive only when it needs to select one block from the list of blocks selected by the listen directive. For example, if the domain example.com is hosted on port 80 at the address 192.168.1.10, a request for example.com will always be served by the first block in the example below, regardless of the server_name directive in the second block.

server {
listen 192.168.1.10;
. . .
}
server {
listen 80;
server_name example.com;
. . .
}

If Nginx has selected several blocks with the same level of specificity, it will then check the server_name directive.

The server_name directive

To further evaluate requests that have identical listen directive definitions, Nginx checks the Host header of the request. This value contains the domain or IP address that the client is requesting.

Nginx looks for the best match of this value in the server_name directive of each block that has passed the previous selection stage. Nginx evaluates this directive using the following formula:

  • First, Nginx tries to find a server block whose server_name value exactly matches the value in the Host header of the request. If such a block is found, it will be used to serve the request. If Nginx finds several exact matches, the first block found is used.
  • If Nginx does not find an exact match, it will try to find a block whose server_name directive begins with the special * character. If Nginx finds such a block, this block will be used to serve the request. If Nginx finds several matches, the most specific match will be used to serve the request.
  • If Nginx has not found matches by the special character at the beginning of server_name, it will look for a block whose server_name value ends with the special * character. If such a block is found, it is used to serve the request. If Nginx finds several matches, the most specific of them will be used to serve the request.
  • If Nginx has not found matches by the special character at the end of server_name, it evaluates blocks whose server_name value uses regular expressions (they are defined by the ~ character before the name). The first block that contains a regular expression in server_name that matches the Host header will be used to serve the request.
  • If no block is found using regular expressions, Nginx selects the default server block for this IP address and port.

For each combination of IP address and port, there is a default server block that is used in case the web server could not find another block. As a rule, this is either the first block in the configuration, or the block that contains the default_server parameter as part of the listen directive (it overrides the first-match search algorithm). There can only be one default_server declaration for each combination of IP address and port.

Examples

If the configuration has a block with a server_name directive whose value fully matches the Host header of the request, the request is passed for processing to that block.

For example, if the Host header of the request is host1.example.com, the web server will select the second server block to serve it:

server {
listen 80;
server_name *.example.com;
. . .
}
server {
listen 80;
server_name host1.example.com;
. . .
}

If Nginx does not find any exact matches, it will look for a block in which server_name begins with the special character. If Nginx finds several matches, the most specific of them will be used to serve the request. For example, if the request specifies the Host header www.example.org, Nginx will select the second block:

server {
listen 80;
server_name www.example.*;
. . .
}
server {
listen 80;
server_name *.example.org;
. . .
}
server {
listen 80;
server_name *.org;
. . .
}

If it was not possible to find a block by the special character at the beginning of the directive, Nginx will look for a block whose server_name value ends with the special character. If it finds several matches, it uses the most specific of them. For example, to process a request with the Host header www.example.com, the web server will use the third server block:

server {
listen 80;
server_name host1.example.com;
. . .
}
server {
listen 80;
server_name example.com;
. . .
}
server {
listen 80;
server_name www.example.*;
. . .
}

If it was not possible to find a block by the special character, Nginx will look for server_name directives that contain regular expressions. The first block whose regular expression in the directive matches the request header will be used to process the request.

For example, to serve a request with the Host header www.example.com, the web server will select the second server block:

server {
listen 80;
server_name example.com;
. . .
}
server {
listen 80;
server_name ~^(www|host1).*\.example\.com$;
. . .
}
server {
listen 80;
server_name ~^(subdomain|set|www|host1).*\.example\.com$;
. . .
}

If none of the lookup mechanisms produced a result, the web server will apply the default server block.

2: Finding the location block

Nginx uses a similar algorithm to find the location block.

Location block syntax

First, let's look at the syntax of the location block. Location blocks are located inside server blocks (or other location blocks) and are used to define how to handle the URI of a request (the part of the request that comes after the domain name or IP address/port).

As a rule, a location block looks like this:

location optional_modifier location_match {
. . .
}

location_match in the example above indicates that Nginx must check the URI of the request. The presence or absence of a modifier in the example above affects how Nginx will search for the location block.

There are the following location block modifiers:

  • (none): if a block has no modifier, the location block is interpreted as a prefix. This means that to determine a match, the specified block will be compared against the beginning of the request URI.
  • =: this block will be selected if the request URI exactly matches the specified location.
  • ~: such a block will be interpreted as a case-sensitive regular expression match.
  • ~*: such a block will be interpreted as a case-insensitive regular expression match.
  • ^~: if this block is selected as the most specific match without a regular expression, then the web server will not perform a search using regular expressions.

Location block syntax examples

As an example of a prefix search, you can use the following location block to respond to URI requests (/site, /site/page1/index.html, or /site/index.html).

location /site {
. . .
}

Below you will find an example of an exact URI match. Such a block will always be used to serve the URI

/page1. It will not respond to a URI request for /page1/index.html. Keep in mind that if this block is selected and the request is served by an index page, an internal redirect will occur to another location block, which will be the actual handler of the request.

location = /page1 {
. . .
}

Interpreting a location block as a case-sensitive regular expression happens in the following example. This block will be used to process requests for /tortoise.jpg, but not for /FLOWER.PNG:

location ~ \.(jpe?g|png|gif|ico)$ {
. . .
}

In the following example, the location block is interpreted as a case-insensitive regular expression. Such a block will be able to process requests for both /tortoise.jpg and /FLOWER.PNG.

location ~* \.(jpe?g|png|gif|ico)$ {
. . .
}

The following block will disable the regular expression search if it is selected as the best match without regular expressions. It can process requests for /costumes/ninja.html:

location ^~ /costumes {
. . .
}

As you can see, modifiers indicate how a location block should be interpreted. However, this does not define the algorithm that Nginx uses to decide which location block to send a request to.

Selecting a location block

Nginx selects a location block in a manner similar to how it selects a server block. It runs a process that determines the best location block for a specific request. Understanding this process is a critical requirement for reliable and precise Nginx configuration.

Keeping in mind the types of declarations we discussed above, Nginx evaluates possible location contexts by comparing the request URI against each of the locations. It does this using the following algorithm:

  • First, Nginx checks all location blocks defined by prefix strings. For this, the location is compared against the full URI string.
  • Then Nginx looks for an exact match. If it finds a location with the = modifier, it stops the search and uses the found configuration.
  • If no exact match is found, the web server performs a search for inexact matches. It looks for the location with the longest matching prefix for the given URI, which is then evaluated as follows:
    • If the location with the longest matching prefix contains the ^~ modifier, Nginx will immediately stop the search and select this location block to serve requests.
    • If the location with the longest matching prefix does not contain the ^~ modifier, Nginx will remember this prefix and continue searching.
  • After Nginx has found and remembered the location with the longest matching prefix, it proceeds to evaluate regular expressions (case-sensitive and case-insensitive). If the location with the longest matching prefix contains any location blocks with regular expressions, Nginx will place them at the beginning of the list of regular expressions to check. Nginx will then sequentially compare the blocks with regular expressions. The first expression that matches the request URI will be selected for processing.
  • If no match with a regular expression is found, Nginx uses the configuration of the previously remembered prefix location.

It is important to understand that, by default, Nginx will serve regular expressions while giving preference to prefix matches. However, it evaluates prefix locations first, allowing the administrator to override this behavior by specifying locations using the = and ^~ modifiers.

It is also important to note that, although prefix locations are usually selected based on the longest prefix (the most exact match), Nginx will stop evaluating regular expressions upon finding the first matching location. This means that the placement of location blocks with regular expressions in the configuration matters a great deal.

Evaluating location blocks

Now we need to understand in which cases the evaluation of location blocks moves on to other locations.

In general, from the moment a location block is selected to serve a request, the request is processed entirely within that context. Only the selected location block and inherited directives determine how the request is processed, and neighboring location blocks cannot interfere with this process.

This is a general rule that will allow you to design location blocks in a predictable way. But it is also important to understand that there are cases when certain directives trigger a new location search within the selected location block. Exceptions to the rule can lead to unpredictable results.

Here are some of the directives that can cause this behavior:

  • index
  • try_files
  • rewrite
  • error_page

The index directive always leads to an internal redirect if it is used to process a request. Exact location matches are often used to speed up the selection process, because this immediately terminates the algorithm. However, if the exact location match turns out to be a directory, there is a chance that the request will be redirected elsewhere for actual processing.

In this example, the first location matches the request URI /exact, but the index directive inherited by the block triggers an internal redirect to the second block for processing the request:

index index.html;
location = /exact {
. . .
}
location / {
. . .
}

If you want the request in the case above to be processed by the first block, you will have to come up with another method for getting the request into the directory. For example, you could set an incorrect index for this block and enable autoindex:

location = /exact {
index nothing_will_match;
autoindex on;
}
location / {
. . .
}

This is one way to prevent the request from being redirected out of the first context, but it is probably not suitable for most configurations. Mostly, an exact match on directories can be useful for operations such as rewriting the request (which also leads to a new location search).

Another case in which a new location search can begin – is the use of 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, to which Nginx will make an internal redirect.

Consider the following configuration:

root /var/www/main;
location / {
try_files $uri $uri.html $uri/ /fallback/index.html;
}
location /fallback {
root /var/www/another;
}

If, in the example above, a request is made for /blahblah, the first location will receive it first. It will try to find a file named blahblah in the /var/www/main directory. If it can't find it, it will look for a file named blahblah.html. Then it will try to find out if there is a blahblah/ directory in /var/www/main. If none of these attempts produce a result, the request will be redirected to /fallback/index.html. This will trigger a new location search, and the request will move on to the second block. It will serve the file /var/www/another/fallback/index.html.

The rewrite directive also affects the block search. When processing a rewrite without parameters or with the last parameter, Nginx will look for a new location block based on the results of the rewrite.

For example, if you modify the last example and add a rewrite to it, you will see that the request is sometimes passed directly to the second location block, without relying on the try_files directive:

root /var/www/main;
location / {
rewrite ^/rewriteme/(.*)$ /$1 last;
try_files $uri $uri.html $uri/ /fallback/index.html;
}
location /fallback {
root /var/www/another;
}

In the example above, a request for /rewriteme/hello will first be handled by the first location block. It will be rewritten to /hello, and the web server will search for a location. In this case, it will again match the first location and be handled by the try_files directive (possibly using an internal redirect to fall back to /fallback/index.html if nothing was found).

However, if a request is made for /rewriteme/fallback/hello, the first block will again respond to the request. This time, the rewrite is applied again, resulting this time in /fallback/hello. The request will then be served by the second block.

A similar situation arises with the return directive when sending 301 or 302 status codes. The difference in this case is that it results in a completely new request from outside the redirect. The same situation can occur with the rewrite directive when using the redirect or permanent flags.

The error_page directive can lead to an internal redirect similarly to how try_files does. This directive is used to define the actions performed when certain status codes are encountered. These actions will probably never be executed if the try_files directive is set, since that directive handles the entire lifecycle of the request.

Consider the following example:

root /var/www/main;
location / {
error_page 404 /another/whoops.html;
}
location /another {
root /var/www;
}

Every request (except those starting with /another) will be handled by the first block, which will serve files from the /var/www/main directory. However, if the file is not found (status 404), an internal redirect to /another/whoops.html will occur, which will trigger a new location block search, which will ultimately end at the second block. This block will serve the file /var/www/another/whoops.html.

As you can see, understanding the conditions under which Nginx triggers a new location block search can help predict the behavior of the web server when processing requests.

Comments

To leave a comment

If you have any suggestion, idea, thanks or comment, feel free to write. We really value feedback and are glad to hear your opinion.
To reply

Lectures and tutorial on "Running server side scripts using PHP as an example (LAMP)"

Terms: Running server side scripts using PHP as an example (LAMP)