Never Ending Security

It starts all here

Understanding the Nginx for Linux

UNDERSTANDING THE NGINX

Nginx is a high performance web server that is responsible for handling the load of some of the largest sites on the internet. It is especially good at handling many concurrent connections and excels at serving static content. Nginx is one of the most popular web servers in the world. It can successfully handle high loads with many concurrent client connections, and can easily function as a web server, a mail server, or a reverse proxy server.

Understanding the Nginx nginx-logo-1 Understanding the NginxWhile many users are aware of Nginx’s capabilities, new users are often confused by some of the conventions they find in Nginx configuration files. Here, we will focus on discussing the basic structure of an Nginx configuration file along with some guidelines on how to design your files.

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. This decision process is what we will be discussing in this guide.

Understanding the Nginx Contexts

One of the first things that you should notice when looking at the main configuration file is that it appears to be organized in a tree-like structure, defined by sets of brackets (that look like { and }). In Nginx parlance, the areas that these brackets define are called “contexts”. They contain configuration details that are separated according to their area of concern. These divisions provide an organizational structure along with some conditional logic to decide whether to apply the configurations within.

Because contexts can be layered within one another, Nginx provides a level of directive inheritance. As a general rule, if a directive is valid in multiple nested scopes, a declaration in a broader context will be passed on to any child contexts as default values. The children contexts can override these values at will.

Directives can only be used in the contexts that they were designed for. Nginx will error out on reading a configuration file with directives that are declared in the wrong context. The Nginx documentation contains information about which contexts each directive is valid in, so it is a great reference if you are unsure.

The Core Contexts

The first group of contexts that we will discuss are the core contexts that Nginx utilizes in order to create a hierarchical tree and separate the concerns of discrete configuration blocks. These are the contexts that comprise the major structure of an Nginx configuration.

The Main Context

The most general context is the “main” or “global” context. It is the only context that is not contained within the typical context blocks and it looks like this:

# The main context is here, outside any other contexts

. . .

context {

    . . .

}

Any directive that exist entirely outside of these blocks is said to inhabit the “main” context. Keep in mind that if your Nginx configuration is set up in a modular fashion, some files will contain instructions that appear to exist outside of a bracketed context, but which will be included within such a context when the configuration is stitched together.

The main context represents the broadest environment for Nginx configuration. It is used to configure details that affect the entire application on a basic level. While the directives in this section affect the lower contexts, many of these aren’t inherited because they cannot be overridden in lower levels.

Some common details that are configured in the main context are the user and group to run the worker processes as, the number of workers, and the file to save the main process’s PID. You can even define things like worker CPU affinity and the “niceness” of worker processes. The default error file for the entire application can be set at this level (this can be overridden in more specific contexts).

The Events Context

The “events” context is contained within the “main” context. It is used to set global options that affect how Nginx handles connections at a general level. There can only be a single events context defined within the Nginx configuration.

This context will look like this in the configuration file, outside of any other bracketed contexts:

# main context

events {

    # events context
    . . .

}

Nginx uses an event-based connection processing model, so the directives defined within this context determine how worker processes should handle connections. Mainly, directives found here are used to either select the connection processing technique to use, or to modify the way these methods are implemented.

Usually, the connection processing method is automatically selected based on the most efficient choice that the platform has available. For Linux systems, the epoll method is usually the best choice.

Other items that can be configured are the number of connections each worker can handle, whether a worker will only take a single connection at a time or take all pending connections after being notified about a pending connection, and whether workers will take turns responding to events.

The HTTP Context

When configuring Nginx as a web server or reverse proxy, the “http” context will hold the majority of the configuration. This context will contain all of the directives and other contexts necessary to define how the program will handle HTTP or HTTPS connections.

The http context is a sibling of the events context, so they should be listed side-by-side, rather than nested. They both are children of the main context:

# main context

events {
    # events context

    . . .

}

http {
    # main context

    . . .

}

While lower contexts get more specific about how to handle requests, directives at this level control the defaults for every virtual server defined within. A large number of directives are configurable at this context and below, depending on how you would like the inheritance to function.

Some of the directives that you are likely to encounter control the default locations for access and error logs (access_log anderror_log), configure asynchronous I/O for file operations (aio, sendfile, and directio), and configure the server’s statuses when errors occur (error_page). Other directives configure compression (gzip and gzip_disable), fine-tune the TCP keep alive settings (keepalive_disable, keepalive_requests, and keepalive_timeout), and the rules that Nginx will follow to try to optimize packets and system calls (sendfile, tcp_nodelay, and tcp_nopush). Additional directives configure an application-level document root and index files (root and index) and set up the various hash tables that are used to store different types of data (*_hash_bucket_size and *_hash_max_size for server_names, types, and variables).

The Server Context

The “server” context is declared within the “http” context. This is the first example of nested, bracketed contexts. It is also the first context that allows multiple declarations.

The general format for server context may look something like this. Remember that these reside within the http context:

# main context

http: {

    # http context

    server {

        # first server context

    }

    server {

        # second server context

    }

}

The reason for allowing multiple declarations of the server context is that each instance defines a specific virtual server to handle client requests. You can have as many server blocks as you need, each of which can handle a specific subset of connections.

Due to the possibility and likelihood of multiple server blocks, this context type is also the first that Nginx must use a selection algorithm to make decisions. Each client request will be handled according to the configuration defined in a single server context, so Nginx must decide which server context is most appropriate based on details of the request. The directives which decide if a server block will be used to answer a request are:

  • listen: The ip address / port combination that this server block is designed to respond to. If a request is made by a client that matches these values, this block will potentially be selected to handle the connection.
  • server_name: This directive is the other component used to select a server block for processing. If there are multiple server blocks with listen directives of the same specificity that can handle the request, Nginx will parse the “Host” header of the request and match it against this directive.

The directives in this context can override many of the directives that may be defined in the http context, including logging, the document root, compression, etc. In addition to the directives that are taken from the http context, we also can configure files to try to respond to requests (try_files), issue redirects and rewrites (return and rewrite), and set arbitrary variables (set).

How Nginx Decides Which Server Block Will Handle a Request ?

First, Nginx looks at the IP address and the port of the request. It matches this against the listen directive of each server to build a list of the server blocks that can possibly resolve the request.

The listen directive typically defines which IP address and port that the server block will respond to. By default, any server block that does not include a listen directive is given the listen parameters of 0.0.0.0:80 (or 0.0.0.0:8080 if Nginx is being run by a normal, non-root user). This allows these blocks to respond to requests on any interface on port 80, but this default value does not hold much weight within the server selection process.

The listen directive can be set to:

  • An IP address/port combo.
  • A lone IP address which will then listen on the default port 80.
  • A lone port which will listen to every interface on that port.
  • The path to a Unix socket.

The last option will generally only have implications when passing requests between different servers.

When trying to determine which server block to send a request to, Nginx will first try to decide based on the specificity of the listendirective using the following rules:

  • Nginx translates all “incomplete” listen directives by substituting missing values with their default values so that each block can be evaluated by its IP address and port. Some examples of these translations are:
    • A block with no listen directive uses the value 0.0.0.0:80.
    • A block set to an IP address 111.111.111.111 with no port becomes111.111.111.111:80
    • A block set to port 8888 with no IP address becomes 0.0.0.0:8888
  • Nginx then attempts to collect a list of the server blocks that match the request most specifically based on the IP address and port. This means that any block that is functionally using 0.0.0.0 as its IP address (to match any interface), will not be selected if there are matching blocks that list a specific IP address. In any case, the port must be matched exactly.
  • If there is only one most specific match, that server block will be used to serve the request. If there are multiple server blocks with the same level of specificity matching, Nginx then begins to evaluate the server_name directive of each server block.

It is important to understand that Nginx will only evaluate the server_name directive when it needs to distinguish between server blocks that match to the same level of specificity in the listen directive. For instance, if example.com is hosted on port 80 of192.168.1.10, a request for example.com will always be served by the first block in this example, despite the server_namedirective in the second block.

server {
    listen 192.168.1.10;

    . . .

}

server {
    listen 80;
    server_name example.com;

    . . .

}

In the event that more than one server block matches with equal specificity, the next step is to check the server_name directive.

Parsing the “server_name” Directive to Choose a Match

Next, to further evaluate requests that have equally specific listen directives, Nginx checks the request’s “Host” header. This value holds the domain or IP address that the client was actually trying to reach.

Nginx attempts to find the best match for the value it finds by looking at the server_name directive within each of the server blocks that are still selection candidates. Nginx evaluates these by using the following formula:

  • Nginx will first try to find a server block with a server_name that matches the value in the “Host” header of the request exactly. If this is found, the associated block will be used to serve the request. If multiple exact matches are found, the first one is used.
  • If no exact match is found, Nginx will then try to find a server block with a server_name that matches using a leading wildcard (indicated by a * at the beginning of the name in the config). If one is found, that block will be used to serve the request. If multiple matches are found, the longest match will be used to serve the request.
  • If no match is found using a leading wildcard, Nginx then looks for a server block with a server_name that matches using a trailing wildcard (indicated by a server name ending with a *in the config). If one is found, that block is used to serve the request. If multiple matches are found, the longest match will be used to serve the request.
  • If no match is found using a trailing wildcard, Nginx then evaluates server blocks that define the server_nameusing regular expressions (indicated by a ~ before the name). The first server_name with a regular expression that matches the “Host” header will be used to serve the request.
  • If no regular expression match is found, Nginx then selects the default server block for that IP address and port.

Each IP address/port combo has a default server block that will be used when a course of action can not be determined with the above methods. For an IP address/port combo, this will either be the first block in the configuration or the block that contains thedefault_server option as part of the listen directive (which would override the first-found algorithm). There can be only onedefault_server declaration per each IP address/port combination.

Examples

If there is a server_name defined that exactly matches the “Host” header value, that server block is selected to process the request.

In this example, if the “Host” header of the request was set to “cyberpunk.example.com”, the second server would be selected:

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

    . . .

}

server {
    listen 80;
    server_name cyberpunk.example.com;

    . . .

}

If no exact match is found, Nginx then checks to see if there is a server_name with a starting wildcard that fits. The longest match beginning with a wildcard will be selected to fulfill the request.

In this example, if the request had a “Host” header of “www.example.org”, the second server block would be selected:

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

    . . .

}

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

    . . .

}

server {
    listen 80;
    server_name *.org;

    . . .

}

If no match is found with a starting wildcard, Nginx will then see if a match exists using a wildcard at the end of the expression. At this point, the longest match ending with a wildcard will be selected to serve the request.

For instance, if the request has a “Host” header set to “www.example.com”, the third server block will be selected:

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

    . . .

}

server {
    listen 80;
    server_name example.com;

    . . .

}

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

    . . .

}

If no wildcard matches can be found, Nginx will then move on to attempting to match server_name directives that use regular expressions. The first matching regular expression will be selected to respond to the request.

For example, if the “Host” header of the request is set to “www.example.com”, then the second server block will be selected to satisfy the request:

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 above steps are able to satisfy the request, then the request will be passed to the default server for the matching IP address and port.

The Location Context

The next context that you will deal with regularly is the location context. Location contexts share many relational qualities with server contexts. For example, multiple location contexts can be defined, each location is used to handle a certain type of client request, and each location is selected by virtue of matching the location definition against the client request through a selection algorithm.

While the directives that determine whether to select a server block are defined within the server context itself, the component that decides on a location’s ability to handle a request is located in the location definition itself.

The general syntax looks like this:

location optional_modifier location_match {

    . . .

}

The location_match above defines what Nginx should check the request URI against. The existence or nonexistence 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 modifiers 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 a case-insensitive regular expression match.
  • ^~: If a carat and tilde modifier is present, and if this block is selected as the best non-regular expression match, regular expression matching will not take place.

Location blocks live within server contexts and, unlike server blocks, can be nested inside one another. This can be useful for creating a more general location context to catch a certain subset of traffic, and then further processing it based on more specific criteria with additional contexts inside:

# main context

server {

    # server context

    location /match/criteria {

        # first location context

    }

    location /other/criteria {

        # second location context

        location nested_match {

            # first nested location

        }

        location other_nested {

            # second nested location

        }

    }

}

While server contexts are selected based on the requested IP address/port combination and the host name in the “Host” header, location blocks further divide up the request handling within a server block by looking at the request URI. The request URI is the portion of the request that comes after the domain name or IP address/port combination.

So, if a client requests http://www.example.com/blog on port 80, the http, http://www.example.com, and port 80 would all be used to determine which server block to select. After a server is selected, the/blog portion (the request URI), would be evaluated against the defined locations to determine which further context should be used to respond to the request.

Many of the directives you are likely to see in a location context are also available at the parent levels. New directives at this level allow you to reach locations outside of the document root (alias), mark the location as only internally accessible (internal), and proxy to other servers or locations (using http, fastcgi, scgi, and uwsgi proxying).

Examples Demonstrating 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/page1/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 /page1. It will not be used to respond to a /page1/index.html request URI. Keep in mind that if this block is selected and the request is fulfilled using an index page, an internal redirect will take place to another location that will be the actual handler of the request:

location = /page1 {

    . . .

}

As an example of a location that should be interpreted as a case-sensitive regular expression, this block could be used to handle requests for /tortoise.jpg, but not for /FLOWER.PNG:

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

    . . .

}

A block that would allow for case-insensitive matching similar to the above is shown below. Here, both/tortoise.jpg and/FLOWER.PNG could be handled by this block:

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

    . . .

}

Finally, this 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 {

    . . .

}

As you see, the modifiers indicate how the location block should be interpreted. However, this does nottell us the algorithm that Nginx uses to decide which location block to send the request to. We will go over that next.

How Nginx Chooses Which Location to Use to Handle Requests

Nginx chooses the location that will be used to serve a request in a similar fashion to how it selects a server block. It 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.

Keeping in mind the types of location declarations we described above, 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.
  • First, 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 (with the = modifier) 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). Nginx tries the regular expression locations sequentially. Thefirst 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, itevaluates prefix locations first, allowing for the administer to 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 stopped when the first matching location is found. This means that positioning within the configuration has vast implications for regular expression locations.

When Does Location Block Evaluation Jump to Other Locations?

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 realize 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

Let’s go over these briefly.

The index directive always leads to an internal redirect if it 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 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 case 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 indexfor 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 reevaluated 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/main;
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 calledblahblah in /var/www/main 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 a directory calledblahblah/ within the /var/www/main 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. This will serve the file /var/www/another/fallback/index.html.

Another directive that can lead to a location block pass off is the rewrite directive. When using thelast parameter with therewrite 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/main;
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 (using the try_files internal redirect we discussed above).

However, if a 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 rewritedirective when using the redirect orpermanent 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/main;

location / {
    error_page 404 /another/whoops.html;
}

location /another {
    root /var/www;
}

Every request (other than those starting with /another) will be handled by the first block, which will serve files out of/var/www/main. However, if a file is not found (a 404 status), an internal redirect to/another/whoops.html will occur, leading to a new location search that will eventually land on the second block. This file will be served out of/var/www/another/whoops.html.

Other Contexts

While the above examples represent the essential contexts that you will encounter with Nginx, other contexts exist as well. The contexts below were separated out either because they depend on more optional modules, they are used only in certain circumstances, or they are used for functionality that most people will not be using.

Some of these contexts are:

  • split_clients: This context is configured to split the clients that the server receives into categories by labeling them with variables based on a percentage. These can then be used to do A/B testing by providing different content to different hosts.
  • perl / perl_set: These contexts configures Perl handlers for the location they appear in. This will only be used for processing with Perl.
  • map: This context is used to set the value of a variable depending on the value of another variable. It provides a mapping of one variable’s values to determine what the second variable should be set to.
  • geo: Like the above context, this context is used to specify a mapping. However, this mapping is specifically used to categorize client IP addresses. It sets the value of a variable depending on the connecting IP address.
  • types: This context is again used for mapping. This context is used to map MIME types to the file extensions that should be associated with them. This is usually provided with Nginx through a file that is sourced into the mainnginx.conf config file.
  • charset_map: This is another example of a mapping context. This context is used to map a conversion table from one character set to another. In the context header, both sets are listed and in the body, the mapping takes place.

The contexts below are not as common as the ones we have discussed so far, but are still very useful to know about.

The Upstream Context

The upstream context is used to define and configure “upstream” servers. Basically, this context defines a named pool of servers that Nginx can then proxy requests to. This context will likely be used when you are configuring proxies of various types.

The upstream context should be placed within the http context, outside of any specific server contexts. The general form looks something like this:

# main context

http {

    # http context

    upstream upstream_name {

        # upstream context

        server proxy_server1;
        server proxy_server2;

        . . .

    }

    server {

        # server context

    }

}

The upstream context can then be referenced by name within server or location blocks to pass requests of a certain type to the pool of servers that have been defined. The upstream will then use an algorithm (round-robin by default) to determine which specific server to hand the request to. This context gives our Nginx the ability to do some load balancing when proxying requests.

The Mail Context

Although Nginx is most often used as a web or reverse proxy server, it can also function as a high performance mail proxy server. The context that is used for directives of this type is called, appropriately, “mail”. The mail context is defined within the “main” or “global” context (outside of the http context).

The main function of the mail context is to provide an area for configuring a mail proxying solution on the server. Nginx has the ability to redirect authentication requests to an external authentication server. It can then provide access to POP3 and IMAP mail servers for serving the actual mail data. The mail context can also be configured to connect to an SMTP Relayhost if desired.

In general, a mail context will look something like this:

# main context

events {

    # events context

}

mail {

    # mail context

}

The If Context

The “if” context can be established to provide conditional processing of directives defined within. Like an if statement in conventional programming, the if directive in Nginx will execute the instructions contained if a given test returns “true”.

The if context in Nginx is provided by the rewrite module and this is the primary intended use of this context. Since Nginx will test conditions of a request with many other purpose-made directives, if should not be used for most forms of conditional execution. This is such an important note that the Nginx community has created a page called if is evil.

The problem is basically that the Nginx processing order can very often lead to unexpected results that seem to subvert the meaning of an if block. The only directives that are considered reliably safe to use inside of these contexts are the return and rewritedirectives (the ones this context was created for). Another thing to keep in mind when using an if context is that it renders atry_files directive in the same context useless.

Most often, an if will be used to determine whether a rewrite or return is needed. These will most often exist in location blocks, so the common form will look something like this:

# main context

http {

    # http context

    server {

        # server context

        location location_match {

            # location context

            if (test_condition) {

                # if context

            }

        }

    }

}

The Limit_except Context

The limit_except context is used to restrict the use of certain HTTP methods within a location context. For example, if only certain clients should have access to POST content, but everyone should have the ability to read content, you can use a limit_except block to define this requirement.

The above example would look something like this:

. . .

# server or location context

location /restricted-write {

    # location context

    limit_except GET HEAD {

        # limit_except context

        allow 192.168.1.1/24;
        deny all;
    }
}

This will apply the directives inside the context (meant to restrict access) when encountering any HTTP methods except those listed in the context header. The result of the above example is that any client can use the GET and HEAD verbs, but only clients coming from the 192.168.1.1/24 subnet are allowed to use other methods.

General Rules to Follow Regarding Contexts

Now that you have an idea of the common contexts that you are likely to encounter when exploring Nginx configurations, we can discuss some best practices to use when dealing with Nginx contexts.

Apply Directives in the Highest Context Available

Many directives are valid in more than one context. For instance, there are quite a few directives that can be placed in the http, server, or location context. This gives us flexibility in setting these directives.

However, as a general rule, it is usually best to declare directives in the highest context to which they are applicable, and overriding them in lower contexts as necessary. This is possible because of the inheritance model that Nginx implements. There are many reasons to use this strategy.

First of all, declaring at a high level allows you to avoid unnecessary repetition between sibling contexts. For instance, in the example below, each of the locations is declaring the same document root:

http {
    server {
        location / {
            root /var/www/html;

            . . .

        }

        location /another {
            root /var/www/html;

            . . .

        }

    }
}

You could move the root out to the server block, or even to the http block, like this:

http {
    root /var/www/html;
    server {
        location / {

            . . .

        }

        location /another {

            . . .

        }
    }
}

Most of the time, the server level will be most appropriate, but declaring at the higher level has its advantages. This not only allows you to set the directive in fewer places, it also allows you to cascade the default value down to all of the child elements, preventing situations where you run into an error by forgetting a directive at a lower level. This can be a major issue with long configurations. Declaring at higher levels provides you with a sane default.

Use Multiple Sibling Contexts Instead of If Logic for Processing

When you want to handle requests differently depending on some information that can be found in the client’s request, often users jump to the “if” context to try to conditionalize processing. There are a few issues with this that we touched on briefly earlier.

The first is that the “if” directive often return results that do not align with the administrator’s expectations. Although the processing will always lead to the same result given the same input, the way that Nginx interprets the environment can be vastly different than can be assumed without heavy testing.

The second reason for this is that there are already optimized, purpose-made directives that are used for many of these purposes. Nginx already engages in a well-documented selection algorithm for things like selecting server blocks and location blocks. So if it is possible, it is best to try to move your different configurations into their own blocks so that this algorithm can handle the selection process logic.

For instance, instead of relying on rewrites to get a user supplied request into the format that you would like to work with, you should try to set up two blocks for the request, one of which represents the desired method, and the other that catches messy requests and redirects (and possibly rewrites) them to your correct block.

The result is usually easier to read and also has the added benefit of being more performant. Correct requests undergo no additional processing and, in many cases, incorrect requests can get by with a redirect rather than a rewrite, which should execute with lower overhead.

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s