Book Image

Instant Nginx Starter

By : Martin Bjerretoft Fjordvald, Martin Fjordvald
Book Image

Instant Nginx Starter

By: Martin Bjerretoft Fjordvald, Martin Fjordvald

Overview of this book

<p>Nginx is an open source web server and a reverse proxy server for HTTP, SMTP, POP3, and IMAP protocols. It is used to deploy dynamic HTTP content on a network using FastCGI and SCGI handlers for scripts and it serves as a software load balancer . Nginx enables the user to save resources compared to other major web servers such as Apache HTTP as well as provides site visitors with a faster load time for an improved user experience.</p><p>Instant Nginx Starter will show you how to install and set up nginx web server by introducing you to basics that covers an understanding of the nginx modules, which will help you to migrate from Apache to achieve high concurrency, performance and low memory usage.</p><p></p><p>Nginx Starter introduces you to the configuration file in its entirety, explaining the importance of different sections. The book then takes you through various examples based on core syntax and specific features enabling you to configure nginx.</p><p></p><p>During the course of the book, you will see how modern technology is merged with nginx by detailing an example based on websockets. The starter guide emphasizes optimizing load time through compression and caching of dynamic responses, while highlighting that nginx can seamlessly become part of a productive ecosystem of the software.</p><p></p><p>Through this book, you will have learned everything you need to control nginx behavior with an understanding over the tools to debug problems and execute plans.</p>
Table of Contents (7 chapters)

Top 9 features you need to know about


While nginx at the core is designed to be a standard reverse proxy and HTTP web server, we can take it much further and use nginx as a central part in our toolchain, if we look into some of the more esoteric modules as well as the ones not included in the default compile. Thankfully, these modules are very often included in the binary packages provided by repositories, so regardless of which method was used to install nginx, they should be available for us to play with.

Compressing site assets is one of the most important methods to optimize the perceived load time of a first time visitor, and even for subsequent page loads when compressing the HTML backend response.

Gzipping

Gzipping the JavaScript, CSS, and HTML responses is of utmost importance if load time is considered important, which naturally means that nginx offers this as a core feature. If we include the optional gzip static module, we can optimize this process even further by compressing the assets ahead of time, so that nginx can merely serve the static gzip file instead of having to compress it on-the-fly.

To start off with, let's look at how to enable normal on-the-fly gzip compression.

gzip             on;
gzip_min_length  100;
gzip_proxied     expired no-cache no-store private auth;
gzip_comp_level  5;
gzip_types       text/plain text/css text/xml text/javascript application/xml application/xml+rss application/x-javascript image/x-icon;
gzip_disable     "msie6";

These directives are valid in an http context, which means that if we specify them in the http block they will apply to every server block, thus enabling us to specify compression only once. Using our knowledge of nginx inheritance from the Quick Start section we can still override this on a server or location basis if required by simply setting the gzip directive to off.

The different directives are as always explained in detail in the documentation; however, here's a brief overview of what each does:

Directive

Description

gzip

On or off, that is enables or disables gzipping.

gzip_min_length

This is the minimum response size in bytes before nginx will compress the response. It Defaults to 20 bytes.

gzip_proxied

This defines if nginx should compress the response when nginx is behind other proxy software, such as Varnish or HAProxy. It defaults to off.

gzip_comp_level

This defines the gzip compression level, default being 1. It gives diminishing returns past level 4, and past level 5 there's rarely any difference at all. Higher levels use more CPU resources.

gzip_types

The mime types to compress. Text/html is always compressed if gzipping is enabled. To compress everything use *, though, this also compresses resources which are already compressed, thus wasting server resources.

gzip_disable

Regex matched against the user agent to determine when to not compress in case the user agent is buggy. msie6 is a special value for Internet Explorer 4 to 6, which were buggy.

Pre-gzipping

Using the pre-gzipping module has the advantage of saving CPU resources, as the site assets will already be stored in a compressed format instead of having to be compressed on each request. Making use of the pre-gzipping module is both simpler and more complicated at the same time. More complicated as we have to gzip the files ourselves, but simpler as there are far less configuration directives. To enable the precompressed gzip module we simply use the following configuration:

gzip_static      on;
gzip_proxied     expired no-cache no-store private auth;
gzip_disable     "msie6";

Immediately, we'll see that the only new directive is really gzip_static which, like the gzip directive, takes an on or off value to enable or disable it.

Gzipping files is a bit outside the scope of this book. It can either be done by hand using the command line gzip application, or automated as part of a build process, but it has to be done outside of nginx.

Using nginx as a full-page micro cache

It's noon and you've just sat down for lunch when your monitoring service sends you a text message saying your start-up's newly launched web service is down. Seconds later your cofounder texts you in a panic that the website is down, and just as his submissions to HackerNews and Reddit got on the front page too. Ars Technica and The Next Web are currently writing articles covering your start-up and the world is literally about to go under if you don't get the website online immediately.

Enter the micro cache. The concept is that any page which doesn't contain user specific information should be cached in nginx, so that the backend application isn't even touched. This relieves load on the backend and allows most applications to handle far more traffic. Normally, an application will have to be written with caching in mind to handle invalidation of cached pages whenever content updates. The micro cache concept handles this by only caching things for a short period of time. If traffic spikes to 20 requests per second, and the micro cache is set to expire after 10 seconds, that's 200 requests the backend application did not have to handle, which makes micro caching a good tool to use when in a pinch.

While the concept of micro cache is simple, the execution can be a bit more complicated depending on the application. The key aspect is to only cache pages that contain no user specific information. If no such thing exists, it's very simple, otherwise we'll need to control when to cache and when not to cache.

There are two approaches to do this. The first is to use the built-in FastCGI cache or the equivalent for the other modules, such as proxy, uWSGI, SCGI, and so on. The second is to use Memcached as a cache, which is agnostic to the proxy method.

The difference between the two methods is that the built-in FastCGI cache is read and write, while Memcached cache is read-only. Essentially, it becomes a question of where the responsibility for writing to the cache lies. With the built-in FastCGI cache the logic is placed in the nginx config, while with Memcached the logic is placed in the application, as it will need to write to the cache itself.

Memcached micro cache

Lets start with the Memcached scenario, as that's simpler from an nginx point of view and largely similar in construct for us to build on later. A basic Memcached micro cache would look like the following in the nginx configuration:

server {
    root /var/www;

    location / {
        try_files $uri /index.php$is_args$args;
    }

    location ~* \.php$ {
        default_type text/html;
        charset      utf-8;

        if ($request_method = GET) {
            set $memcached_key $request_method$request_uri;

            memcached_pass host:11211;
            error_page     404 502 504 = @nocache;
        }

        if ($request_method != GET) {
            fastcgi_pass backend;
        }
    }

    location @nocache {
        fastcgi_pass backend;
    }
}

In the preceding configuration, the important aspects take place inside the location to handle PHP requests. Specifically, the variable $memcached_key is the most important, as this defines the key to request from Memcached.

A potential complication here is if pages with user data and without user data share the same request URI. In this case, extra configuration is needed to check if a page contains user data. This is always application specific, but common methods are checking for cookies via $http_cookie or checking the URL arguments through $args.

Another thing to notice is that only GET requests use the cache, anything not a GET request will instead fastcgi_pass to our backend, thus bypassing the cache.

If a request passes all the validation and is sent to Memcached and Memcached returns a 404 not found status, error_page will send the request to the @nocache named location, which will fastcgi_pass to our backend. The backend is then responsible for populating the proper key in Memcached for the next request to use.

As the application is writing to the cache here, remember to set the cache expire time to something low enough that we won't have stale cache entries for long when the application date updates.

Built-in FastCGI cache

Using the built-in caches is very similar in construct to the previous config example. The main difference is that not only do we have to define when to read from the cache, but also when to write to it. A typical configuration would look like the following:

fastcgi_cache_path /var/cache/nginx levels=1:2 keys_zone=microcache:5m max_size=500m;

server {
    root /var/www;

    location / {
        try_files $uri /index.php$is_args$args;
    }

    location ~* \.php$ {
        set $no_cache "";

        # Verify request method is GET or HEAD.
        if ($request_method !~ ^(GET|HEAD)$) {
            set $no_cache "1";
        }

        # Check if a nocache cookie is set, for instance after handling a POST.
        if ($http_cookie ~* "_nocache") {
            set $no_cache "1";
        }

        fastcgi_no_cache $no_cache;
        fastcgi_cache_bypass $no_cache;

        fastcgi_cache microcache;
        fastcgi_cache_key $request_method$request_uri;

        fastcgi_cache_valid 200 5s;
        fastcgi_cache_use_stale updating;

        fastcgi_pass backend;
    }
}

As can be seen, the concept is largely the same. Set up the cache keys_zone, figure out whether to cache or not and finally set the cache key. To fully explain what's going on, let's have a look at what the different directives actually do.

Directive

Description

fastcgi_cache_path

Sets the path to store cached responses under. Also names the key zone associated with this cache path and specifies how much metadata and data can be stored there. In this example, keys_zone is called micro cache.

fastcgi_no_cache

Specifies whether to write to the cache or not. Anything other than an empty string or the value numeric 0 will cause it to not write to the cache.

fastcgi_bypass_cache

Specifies whether to read from the cache or not. Anything other than an empty string or the numeric value 0 will cause it to not read from the cache.

fastcgi_cache

Specifies keys_zone to use. In this example, the keys_zone used is micro cache.

fastcgi_cache_key

The key used to identify data in the cache.

fastcgi_cache_valid

Sets the caching time for a given response code. In this example, we want to cache only 200 responses and we will cache them for 5 seconds. Our application can override this directive using the X-Accel-Expires header from the X-Accel module or by using standard caching headers Expires and Cache-Control.

fastcgi_cache_use_stale

Specifies when the cache will use a cache entry after it's expired. In this example, we use updating to allow us to use the cache while it's being updated, thus preventing a sudden flood of connections when a key expires.

Using nginx behind other proxies

While nginx can certainly be used as the only reverse proxy in our stack, there are scenarios where we might want to use alternative software in front of nginx because we have in-house expertise or are already using them. Popular choices here are Varnish and HAProxy.

In this case we can have nginx handle such a scenario transparently using the optional module Real IP. With this we can have nginx transparently replace the variables referencing an IP with the IP of the proxy, thus keeping logs and the configuration of the same while giving us the ability to turn frontends on and off.

There are only three directives associated with the real IP module, thus making it fairly simple to implement and understand.

set_real_ip_from  192.168.1.0/24;
set_real_ip_from  192.168.2.1;
set_real_ip_from  2001:0db8::/32;
real_ip_header    X-Forwarded-For;
real_ip_recursive on;

Directive

Description

set_real_ip_from

This specifies an IP to enable the real IP module from. Preventing random people from pretending to be a frontend to nginx. This can be specified multiple times.

real_ip_header

This specifies the header to get the real IP from. X-Forwarded-For and X-Real-IP are the most commonly used. This defaults to X-Real-IP.

real_ip_recursive

This specifies the IP to use. If off, this will use the last address in header defined by real_ip_header. If on, this will search the IP list until it finds one not in the trusted IP list. This is useful when a request has been forwarded multiple times. This defaults to off.

Setting up secure downloads

nginx has a feature called X-Accel which is meant as a replacement for the mod_sendfile functionality found in Apache httpd and lighttpd. The concept is mostly the same. A request is sent to a backend application, which can then do whatever it needs to do, for instance it might log a download or validate user credentials. Once the backend application is done doing its work it issues a non-standard HTTP header X-Accel-Redirect with a path to the file relative to the document root. nginx will detect this header and look for a matching location based on the path sent. An example of this would be a PHP backend application issuing a header X-Accel-Redirect, that is, /video/birthday/dad.mp4.

In nginx, we would then have the following configuration:

server {
    root /var/www;

    location /video {
        root /mnt/data;
    }
}

In this scenario, nginx would then look for the file at the path /mnt/data/video/birthday/dad.mp4. If the file is not found it will send a 404 status error; if the file is found it will start sending the file to the end user, thus relieving the backend application of this.

nginx has a number of X-Accel headers available.

Header

Description

X-Accel-Redirect

Specifies a URI relative to the root directive in nginx configuration to the file to send to the user.

X-Accel-Buffering

Specifies whether to allow nginx to buffer the connection or not. Turn off if doing Comet style application. Defaults to yes.

X-Accel-Charset

Specifies the character set of the connection. Defaults to utf-8.

X-Accel-Expires

Used to control whether nginx will cache the application response or not. Defaults to off.

X-Accel-Limit-Rate

Specifies a rate limit for the connection.

Doing GeoIP lookups

To do a GeoIP lookup, nginx will need the MaxMind GeoIP database. Both the paid and free versions are compatible with this module. The free version can be downloaded from:

http://dev.maxmind.com/geoip/geolite

Every directive in this module has to be defined in the http section and looks like the following:

geoip_country         /var/data/GeoIP.dat;
geoip_city            /var/data/GeoLiteCity.dat;
geoip_proxy           192.168.2.0/24;
geoip_proxy_recursive on;

Directive

Description

geoip_country

Specifies the path to the country level GeoIP database.

geoip_city

Specifies the path to the city level GeoIP database. This database also contains the data from the country database.

geoip_org

Specifies the path to the organization level GeoIP database. The GeoIP organization database is a paid-only database that nginx also supports.

geoip_proxy

When nginx is used behind other proxy software, this can be used to specify the IP of that proxy and have nginx do a lookup on the IP in X-Forwarded-For instead.

geoip_proxy_recursive

Functionally similar to real_ip_recursive from the using nginx behind other proxies example.

When the proper database is loaded into nginx, the following variables will become available to be used through the config, for instance in the access log or to be passed on to a backend.

Variable

Description

$geoip_country_code
$geoip_city_country_code

Variable name depends on the database specified. Contains the two letter country code.

$geoip_country_code3
$geoip_city_country_code3

Variable name depends on the database specified. Contains the three letter country code.

$geoip_country_name
$geoip_city_country_name

Variable name depends on the database specified. Contains the full country name.

$geoip_city_continent_code

Contains the two letter code for the continent. Only available in city database.

$geoip_dma_code

Contains US region DMA code. Only available in city database.

$geoip_latitude

Contains the latitude of the users location. Only available in city database.

$geoip_longitude

Contains the longitude of the users location. Only available in city database.

$geoip_region

Contains the two symbol country region code. Only available in city database.

$geoip_region_name

Contains the full country region name. Only available in city database.

$geoip_city

Contains the full city name. Only available in city database.

$geoip_postal_code

Contains the postal code of the city. Only available in city database.

$geoip_org

Contains the organization name. Could for instance be a university. Only available in organization database.

Limiting user requests

There are two ways to limit requests in nginx, concurrent requests and frequency of requests. Both can be used simultaneously and multiple times on different factors. For instance, we can limit concurrent requests per IP while we limit concurrent requests per server block.

To achieve this, nginx has two modules; one which limits concurrency and the other which limits frequency.

Limiting concurrent connections

To limit concurrent requests, we use the limit conn module. The concept is fairly simple, we create a memory zone based on a variable and nginx will then track concurrent requests grouped by this variable. We could, for instance, use the $server_name variable to limit concurrent requests per vhost, or we could use $binary_remote_addr to limit on a users IP.

limit_conn_zone $binary_remote_addr zone=perip:5m;

server {
    location /download/ {
        limit_conn perip 1;
        limit_conn_log_level error;
    }
}

Directive

Description

limit_conn_zone

This creates the memory zone. This also specifies the variable to limit based on as well as the maximum size of the memory zone.

limit_conn

This specifies which zone to limit by and how many concurrent connections to allow.

limit_conn_log_level

This specifies the log level required before the module will log that the concurrent connection limit was exceeded. This defaults to error. Generally, it is not advised to set it lower unless needed, as it can quickly flood the error log and hide more useful data.

Limit frequency of connections

To limit the frequency of connections we can use the limit req module. It's syntactically similar with only some minor changes to control rate instead of concurrency.

limit_req_zone $binary_remote_addr zone=one:5m rate=1r/s;

server {
    location /search/ {
        limit_req zone=one burst=5;
        limit_req_log_level error;
    }
}

Directive

Description

limit_req_zone

This creates the memory zone. This specifies the variable to limit based on the variable used as well as the maximum size of the memory zone and the rate at which connections should be allowed. Requests exceeding this rate will be buffered until they reach the limit set by burst at which point they will return 503 instead.

limit_req

This specifies which zone to limit by and the size of the request buffer, called burst.

limit_req_log_level

This specifies the log level required before module will log that the connection frequency limit was exceeded. This defaults to error. Generally, it is not advised to set it lower unless needed, as it can quickly flood the error log and hide more useful data.

Using nginx for streaming videos

Streaming videos with nginx is extremely easy. nginx has two optional modules for streaming videos, FLV and MP4, which enable it to stream flash video formats and MP4 containers with H.264/AAC encoding. These modules are compatible with all the traditional Flash and HTML5 players available today.

Streaming FLV files

The FLV module is the simplest of the two and contains only a single directive. To enable it, simply specify flv in a location as follows:

location ~ \.flv$ {
    root /var/www/video;
    flv;
}

That's literally everything there is to FLV streaming on the nginx side. If the .flv files are properly prepared with metadata and keyframes, they should stream smoothly and be seekable with this.

Streaming MP4 files

The MP4 module is pretty much exactly the same with only a few extra directives for additional control.

location ~ \.mp4$ {
    root /var/www/video;

    mp4;
    mp4_buffer_size     512k
    mp4_max_buffer_size 10m;
}

The buffers control how much memory nginx can use to process the file. This is only limiting during metadata parsing where a large buffer may be required. For this the maximum buffer size becomes relevant. If it's set too small, nginx will output a 500 status error and log the error as:

/var/www/video/file.mp4" mp4 moov atom is too large: 12583268, you may want to increase mp4_max_buffer_size

Using WebSockets with nginx

Version 1.3.13 introduced connection upgrading support to nginx, which means WebSocket support. As WebSockets use the standard HTTP protocol for the initial handshake, nginx can make WebSocket support part of the standard proxy module. This means that all the features available to normal HTTP backends are also available to WebSocket proxying.

The configuration required for handling connection upgrading is as follows:

location /chat/ {
    proxy_pass http://backend;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

A few things to notice about WebSocket support are that they can time out just like any other HTTP proxied request. WebSockets are affected by proxy_read_timeout that defaults to 60 seconds. The keepalive feature in nginx is not of use here, as keepalive pings are empty packets and as such contain no data for nginx to pass to the backend. To combat this, you either need to raise the time out, or implement your own keepalive ping message. The added benefit of the latter solution is that it will also function as a health check for the connection itself.