Nginx by examples: the basics

Nginx is a very popular http server/rev proxy and can be used in multiple configurations. Knowing how to configure it can literally save your day!

Getting Started

To start with let’s install the latest version of Nginx from the official Nginx PPA (under Ubuntu)

sudo -s
add-apt-repository ppa:nginx/stable
apt-get update
apt-get install nginx

Basic conf

Once it’s installed, Nginx comes with the following configuration (that I’ve slightly tweaked):

# runs worker processes nginx as www-data
user www-data;
# sets the number of worker processes to the number of cores as automatically identified by nginx at sturtup
worker_processes auto;
pid /run/nginx.pid;

events {
	# the number of incoming and outgoing connections per worker
	worker_connections 768;
}

http {
	# nginx core optimizations to serve files efficiently
	sendfile on;
	tcp_nopush on;
	tcp_nodelay on;
      
	# the keepalive on http/1.1 connections
	keepalive_timeout 65;
        
	# internal parameter to speed up hashtable lookups
	types_hash_max_size 2048;
	
	# sends the nginx version in the server header
	# server_tokens off;
       
	# default mime type mapping (from extensions)
	include /etc/nginx/mime.types;
	
	# mime default if the previous mapping fails
	default_type application/octet-stream;

	# SSL settings
	ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
	ssl_prefer_server_ciphers on;
    
	# default logs
	access_log /var/log/nginx/access.log;
	error_log /var/log/nginx/error.log;
	
	# turns GZIP on but only for text/html mime types by default
	gzip on;
	gzip_disable "msie6";

	# gzip_vary on;
	# gzip_proxied any;
	# gzip_comp_level 6;
	# gzip_buffers 16 8k;
	# gzip_http_version 1.1;
	# gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

	# additional configuration
	include /etc/nginx/conf.d/*.conf;
	
	# Virtual Host Configs
	include /etc/nginx/sites-enabled/*;
}

First look

The first thing to notice is that nginx directives tend to be very expressive and a small change can make a big difference. This makes the config files very concise (as opposed to Apache’s)

Secondly the directives can be expressed in any order and they are evaluated all together. Therefore two contradicting statements are not allowed (like with Apache).

Nginx processes

Nginx is an event based web server. For maximum performance the number of processes should always be equal to the number of cores available as each process affinity is automatically adjusted to match each core with only one Nginx process.

Setting worker_processes to something higher than the number of cores is useless and does result in performance degradation

sendfile, tcp_nopush, tcp_nodelay

One of the reasons nginx got so popular so quickly is the ability to serve static content very fast and at scale. The original secret behind it sits with these 3 directives. If you serve a lot of static files the default values work just fine. If you serve primarily dynamic content things can be tweaked a bit.

Gzip

By default the Gzip configuration is limited to gzipping only text/html content types. Also the default compression level 6 is too high and may cause too much stress on the CPU, limiting your throughput just to save little bandwidth.

If you are hosting on AWS consider bringing gzip_comp_level down to 1 or 2 and Always uncomment the gzip_types directive. Also remember that compressing binary files like images and videos may make very little sense (as they are already compressed)

Virtual hosts

The default virtual host located at /etc/nginx/sites-available/default looks something like:

server {
	# sets this virtual host to listed on port 80 (ipv4 by default)
	listen       80;
	
	# sets the server name for virtual host resolution
	server_name  localhost;
	
	# custom error and access logs location
	error_log logs/host.error.log error;
	access_log  logs/host.access.log;
	
	# the default location block
	location / {
		# the root directory to use to serve files
		root   /usr/share/nginx/html;
		
		# tries to match the incoming URI with more than one possible file/directory on disk
		try_files $uri $uri/index.html $uri.html =404;
		
		# what files to serve when a directory is matched
		index  index.html index.htm;
	}

	# deny access to .htaccess files, if any
	location ~ /\.ht {
	    deny  all;
	}
}

Server name and listen

The server_name directive drives the virtual host resolution process and can be a source of headaches in case things are not configured properly.

It accepts multiple values in the form or simple strings, wildcards and regexes but unless you really know what you are doing it’s safe to stick to simple strings only

Please refrain from using:

server {
     ...
     server_name _;
     ...
}

as it’s a no match value that should never be used. _ will never match any server name, hence Nginx will resolve ambiguous requests using the first server{} block encountered which is most likely not what you wanted

The correct solution is to use

server {
     ...
     listen 80 default_server;
     ...
}

Location block resolution

This is the trickiest bit of all to understand. When a request arrives Nginx tries to respond using the most appropriate location block based on multiple and somehow complex rules.

A location can either be defined by a prefix string, or by a regular expression. Regular expressions are specified with the preceding “~*” modifier (for case-insensitive matching), or the “~” modifier (for case-sensitive matching). To find location matching a given request, nginx first checks locations defined using the prefix strings (prefix locations). Among them, the location with the longest matching prefix is selected and remembered. Then regular expressions are checked, in the order of their appearance in the configuration file. The search of regular expressions terminates on the first match, and the corresponding configuration is used. If no match with a regular expression is found then the configuration of the prefix location remembered earlier is used.

If the longest matching prefix location has the “^~” modifier then regular expressions are not checked.

Let’s try and simplify:

location = / {
    # some other config here
 }

to reduce the load on the server

Comments

comments powered by Disqus

Subscribe to my newsletter