// you’re reading...

General

Locations and conditional rewrites in Nginx

jamesholden.net runs on nginx, and has done ever since the anti-terror billboard went totally viral.

Whilst many people haven’t heard of nginx, it’s got a fair few success stories to its name and powers some major sites including Wordpress.com and FastMail.FM.

With nginx, many operations beyond serving of static files are treated by nginx as a proxy operations, whether it’s passing the request to one or more back-end servers or to a CGI handler such as PHP. For example, the server that runs jamesholden.net also runs a subversion repository but nginx can’t do that by itself, so it passes any requests for the subversion repository to an instance of Apache running behind the scenes. There’s a huge amount of flexibility afforded in how a request is handled.

The configuration file format used by nginx differs from that of Apache in that nginx uses braces to define nested sections and allows conditional tests to determine whether a section of the configuration should be included for a particular request, while Apache uses nested tags in angle brackets to open and close sections.

A basic server configurations looks similar to this:

http {
    server {
        listen 1.2.3.4:80;
        server_name testing.example.com;
        root /var/www;
    }
}

This is analogous to an Apache style <VirtualHost> directive, complete with ServerName and DocumentRoot. The location block is fundamental to tailoring the behavior of nginx as the expression that follows can be used to handle different URI paths in different ways. Without this flexibility nginx would not be able to pass PHP scripts to the fastcgi backend or proxy requests to back-end systems. The location does not need to be a path – it can also be a label which we can use to break out of another location under certain circumstances.

At present, all this basic example above will do is serve static pages from the document root /var/www. If we want to serve PHP files, we would need to include an additional location with a pattern to match requests ending in .php. This would be placed within the server block.

location ~* \.php$ {
    fastcgi_pass 127.0.0.1:10005;
}

Assuming that there is a fastcgi handler living on localhost port 10005, and that the rest of the fastcgi configuration has been included at an earlier point in the config, we can now pass PHP scripts to the fastcgi handler. In the location directive, the ~* indicates that pattern is case-insensitive, and the \.php$ is a regular expression that matches the string “.php” at the end of the request. nginx prioritises the PHP location directive because it uses a regular expression.

You may specify location matches without the use of regular expressions. This can be useful to ensure that static content is handled as a priority. For example:

location = / {
    # Matches only requests for the home page (and only the home page)
    # This can be useful to serve a cached version of the home page on a high volume site, yet
    # allow for dynamic content on the rest of the site
}

location ^~ /static/ {
    # Matches requests beginning /static/
}

location *~ \.bin$ {
    # Matches only files ending in .bin that are not in /static/
}

Only one location will match per request unless we re-write it, so it is not possible to define overrides on anything other than the URI using location directives. You can do this within a directive block though, using conditional statements.

Two useful directives are the “root” directive and the “alias” directive. At first sight, these may seem to be similar, but they are subtly different. For example:

location ^~ /docs/ {
    root /usr/share/web;
    # Requests for /docs/foo.txt will be served from /usr/share/web/docs/foo.txt
}

location ^~ /icons/ {
    alias /var/www/icons;
    # Requests for /icons/error.png will be served from /var/www/icons/error.png
}

A caveat is that the alias directive cannot be used in a location based on a regular expression.

The final type of location directive is a named location. This can be used in conjunction with a “try_files” directive to allow static files to be served, while passing other requests to a CMS such as wordpress, eg:

location / {
    root /var/www;
    try_files $uri @wordpress;
}

location @wordpress {
    rewrite ^(.*)$ /index.php?q=$1 last;
}

location ~ \.php$ {
    fastcgi_pass 127.0.0.1:10005;
}

This works because the @wordpress location transforms the request into a call to index.php. This causes the request to be re-entered, where it will match the third location block and be passed to the PHP fastcgi handler for execution. There are multiple ways of achieving this, however.

We just used the rewrite directive to transform the URL into another. Just like Apache, we can use regular expressions to match particular URLs and to transform the request. A common use for this is to provide redirection from bare domain names to their www counterparts or vice-versa, for example this is used on jamesholden.net to bounce visitors off www.jamesholden.net to jamesholden.net, which is my preferred URL:

server {
    listen 1.2.3.4:80;
    server_name www.jamesholden.net;
    rewrite ^(.*) http://jamesholden.net$1 permanent;
}

In this example, we don’t even need to use a location directive because the entire site is being redirected. The “permanent” parameter on the rewrite rule ensures that nginx issues an HTTP 301 response.

Conditional statements can be used to check server variables from within a location directive. You could use this to prevent image hotlinking as follows:

location ~* \.jpg$ {
    if ( $http_referer !~ ^http://jamesholden.net/ ) {
        rewrite ^(.*) /nostealing.jpg last;
    }
}

location = /nostealing.jpg { }

Requests for .jpg files where the referer does not begin http://jamesholden.net/ will be re-written to /nostealing.jpg. The final empty location ensures that the replacement image is not rewritten itself, otherwise a loop would ensue.

These examples are just the tip of the iceberg of what can be done with nginx, so feel free to share your own examples in the comments section. Thanks for reading!

Discussion

No comments for “Locations and conditional rewrites in Nginx”

Post a comment