Nginx Cookbook

Posted on:

Interested in using Nginx, the most popular web server worldwide(probably true) for your self-hosted services? And not so interested in reading the boring documentation? Then this is the one-stop guide for you. I’ve written this for future references and others who want to use Nginx for small-scale application deployment.

Before we start

I’m expecting you have a development/deployment environment running Linux. For installation, there should be prebuilt binaries for most Linux distros available. You should follow the official guide for this. After installation, you should be able to find the configuration named nginx.conf in one of these directories: /etc/nginx , /usr/local/nginx/conf, or /usr/local/etc/nginx. A default installation comes with examples inside the configuration directory. You could start from scratch and remove nginx.conf.

Signals

You can use the following command to send a signal to Nginx. They are pretty self-explanatory. Note: This is not the complete list. See Controlling nginx on the official site.

nginx -s (stop|quit|reload)

For example, after editing the configuration file, you should execute nginx -s reload for Nginx to read your changes.

Configuration Format

# context: global
events {
  worker_connections 1024;
}
http {
    # block name: http, context: http
    
    server {
        #block name: server, context: http->server
        listen 80;
        server_name example.com;
        
        location / {
            #block name: location, context: http->server->location
            
            #this is a directive.
            root /www/data;
        }
    }
}

The lines starting with # are comments. Note the space between the block name and the braces.

If you’ve learned OOP before, you should be already familiar with this kind of structure. If not, each block has a name, and its body surrounded by curly brackets. The content of a block would only affect this block, and its children( inner-blocks). Anything that doesn’t belong to a block are in the main(global) context.

A directive is a command or a configuration entry, in the format of:

name param1 param2 paramN;

Notice the semicolon at the end of the line, it’s required at the end of a directive or else Nginx will scream at you. If you like to have the directives aligned properly, you could optionally use more than one spaces between parts.

Events Block

The events block is always required. It should reside in the main context, so make sure you don’t put it inside any other blocks. I’ll recommend you to copy this part as is if you don’t fully understand what you’re doing.

events {
  worker_connections 1024;
}

Servers Block

By defining a server block, Nginx will create a virtual server for you, which will be handling your web traffic. You could the following directives to apply different filters to the traffic, after which this server will process.

listen

This server will listen on port 80.

listen 80;

If you have multiple servers on the same port, requests that doesn’t match any other server will be routed to this one:

listen 80 default_server;

server_name

One or more entries should be specified here. You can use IP addresses, domains, etc. The Host header will be matched against each item in your list. The server names can include an asterisk(*) replacing the first or last part of a name. And you can also use regular expressions, which should be proceeded with a tilde(~).

server_name example.com;
server_name *.example.com;
server_name example.*;
server_name ~^www\d+\.example\.com$;

Recipes

Serving Static Files

server {
    location / {
        root /data/www;
    }

    # also use regex
    location ~* \.(gif|jpg|png)$ {
        root /data/www/imgs;
    }
}

The path of a request: example.org/this/is/the/path.png?not=included

It’s that simple. The location block will match the path of the request. Then the path is appended to the value of your root directive. If the file exists, it returns the file with code 200 OK. If not, code 404 NOT_FOUND is returned. When a request matches more than one location blocks, the most specific prefix location is used. This means /a/b prioritised instead of /a

Custom error pages

server {
    error_page 404 /www/error/404.html
}

Replace 404 with any error codes that you would like to serve a custom error page. Need a list?

Reverse Proxy

A reverse proxy is helpful if you want to build a simple API gateway, or when you want to add SSL to an application using HTTP(covered in recipe “SSL”).

server {
    location / {
        proxy_pass http://localhost:8080;
    }
}

Similar to the root directive mentioned above, proxy_pass works the same way. The path of the request will be appended to your server of choice, and nginx will request that URL and pass the response back to the client(this is called reverse proxy).

Setting Headers for Origin

proxy_set_header Content-Type "application/json";

Rewrite headers for the request sent to the origin.

Origin: the real web server behind your proxy server(which is Nginx).

Rate Limit/Speed Limit

Note: This only limits the speed of reading the response from the proxied server. Look below if you want to set up per-client rate limiting.

proxy_limit_rate 1k;

The rate(1k=1024) here is specified in bytes per second(Bps), not to be confused with bits per second(bps).

Authentication

location / {
    allow  192.168.1.1;
    auth_basic "wrong password";
    auth_basic_user_file conf/htpasswd;
    satisfy any;
}

Multiple authentication methods could be used for one block. Use the satisfy directive to declare whether to apply any or all authentication methods for a request. I hope that makes sense.

The directives shown in this recipe could all be used in http, location and server blocks.

IP Address Filter

You could deny all access to your services and only allow <your ip>.

allow 192.168.1.1;
deny 1.1.1.1;

# also CIDRs
allow 192.168.8.1/24;

# and also "all"
deny all;

Basic Auth

# a authentication realm is specified here. Read more: https://stackoverflow.com/questions/12701085/what-is-the-realm-in-basic-authentication
auth_basic           "my realm";
auth_basic_user_file conf/htpasswd;

A file should be specified for auth_basic_user_file in the following format:

name1:password1
name2:password2:comment
name3:password3

The password could be generated with the command openssl passwd. The result is hashed, so no need to worry about saving them on your drive.

JWTs

This is a good option if you have an SSO or similar set up on the same domain. It’s too complicated for this tutorial so please refer to the official document.

Rate Limiting

If you wish to protect your services from bots and scrapers, something like fail2ban or a WAF could be more useful. They provide much granular rate limit strategies.

This recipe skips some of the arguments that’s not commonly used for normal users. Refer to the official docs for more details.

limit_req_zone

Nginx uses the “leaky bucket” method to determine which client should be rate limited. Each key(client) gets a bucket that has a set capacity. Each request will fill up that bucket by a bit. When the bucket overflows, the client is rate limited and an error will be returned. The bucket also leaks at a fixed rate, which enables you to control the average flow of requests and allow bursts when the client is in need.

limit_req_zone is used to define a zone. It should only be used in the http context.

limit_req_zone key zone=name:size rate=rate;
limit_req_zone $binary_remote_addr zone=auth_zone:10m rate=3r/m;

$binary_remote_addr here is used as the key. It’s a variable which would be replaced by the byte representation of the client IP address. Variables will be explained in a later chapter.

auth_zone here should be replaced by your zone name, and 10m means 10 megabytes of memory will be used to store the states of the zone. You shouldn’t set this to a value too small, nor too big. 1 megabytes could store up to 8k states(8000 different clients in this case).

rate is specified in requests per second(r/s), or requests per minute(r/m).

limit_req

server {
    # also in the server context
    limit_req zone=my_service;
    
    location /login {
        # use different rules for different contexts
        limit_req zone=my_service_auth;
    }
}

SSL/HTTPS

If you haven’t got an SSL certificate ready, you should consider Lets Encrypt, it’s free. Install and run Certbot to get free HTTPS certificates automatically renewed forever( technically).

After acquiring the public and private keys, add the following directives to your http block.

http {
    listen 443 ssl;
    ssl_certificate /path/to/public_key.pem;
    ssl_certificate_key /path/to/private_key.pem;
}

Don’t forget to tell Nginx to listen on port 443 for SSL traffic.

If you have Certbot installed, use this command to have Certbot edit your Nginx configuration automatically.

sudo certbot --nginx

Logging

Use the default log format and a custom log file:

access_log /path/to/log.log;

For advanced formatting and performance tuning, please refer to the official docs.

PHP via FastCGI

server {
    location / {
        include snippets/fastcgi-php.conf;
        fastcgi_pass /var/run/php-fpm.sock;
    }
}

A default Nginx installation should provide you with $nginx_root$/snippets/fastcgi-php.conf which also includes $nginx_root$/fastcgi.conf. These two files has some boilerplate config that you only need to include for PHP. Note only PHP is supported here, so if you’re using something else, you’ll have to configure the ngx_http_fastcgi_module module by yourself.

fastcgi_pass works just like proxy_pass which tells Nginx to pass the request to the FastGCI processor.

Afterwords

And that’s it. Hope you now have some idea about how to config Nginx to fit your own needs. Thanks for reading.

Continue reading: Nginx Full Example Configuration