(Web) Serving Raspberry Pi: Part 2 - nginx

Welcome to part two of my (Web) Serving Raspberry Pi series, this time we'll be setting up the Web Server itself, using nginx.


"Why nginx? What about Apache?"

Well, I'm ultimately intending to use Ghost and they strongly recommend it, but there are quite a few other great reasons to use nginx over Apache.

Performance and scalability

There's a great article about it over on the nginx site, but in a nutshell: nginx is lighter weight and faster, perfect for a Raspberry Pi. Actually, that makes perfect sense because nginx was specifically designed to be faster and more scalable than Apache. Well, some would argue about that till they are blue in the face, but nginx most certainly is faster out of the box. That's not to say that Apache can't be pretty nippy too, it just takes a lot more tweaking and optimisation to make it competitive, and that removes one of Apache's main benefits - the comparative ease of setting it up.

Dynamic content

While nginx is the clear winner for static content, you could spend days arguing over which is best for serving dynamic content.

Apache (as it comes from the Raspbian package) benefits from having a lot of the more popular methods for serving dynamic content either built in out of the box or activated very easily via a module (such as mod_php). Whereas nginx offloads dynamic content to an external processor (e.g. php-fpm).

Because of the ease of activating modules, Apache certainly wins for simplicity when it comes to dynamic content. By way of example: It's a lot easier to setup php on Apache than it's is on nginx. However, there are benefits to offloading dynamic content to an external processor. It means nginx only needs to call it when absolutely necessary, making it theoretically faster most of the time. Apache can be setup that way, but again it loses the ease of configuration advantage.


To cut a long story short and over simplify matters somewhat: If you don't fancy a deep dive into configuration and optimisation, nginx will give you the best performance.


Ok, so we're using nginx, now what? We install it of course! As previously you will need to run most of the following with root privileges (or just use sudo before each of the commands below):

$ sudo -s

Install nginx

This part is quite straight forward, because Raspbian contains a package for nginx. Therefore, we just need to install that:

$ apt-get install nginx

Once nginx is installed successfully you should be able to point a browser to your Raspberry Pi's IP address (run hostname -I if you don't know what it is) and see the default nginx page.

Configure nginx

While it runs perfectly well at this stage we need to start adjusting nginx for our needs. The main configuration file for nginx is nginx.conf, so we begin by editing that:

$ nano /etc/nginx/nginx.conf

Start by changing # multi_accept on; to multi_accept on;, this uncomments the line and allows nginx to accept as many connections as it can, rather than one at a time. Next we should tweak compression by adding:

gzip_min_length 1100;
gzip_vary on;
gzip_proxied any;
gzip_buffers 16 8k;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/rss+xml text/javascript image/svg+xml application/x-font-ttf font/opentype application/vnd.ms-fontobject;

to the gzip section of nginx.conf. Here we are making sure that only files over a certain size are compressed, setting the gzip buffers, and ensuring we compress as many appropriate types of file as possible.

Next we change # server_tokens off; to server_tokens off;. Uncommenting this line prevents nginx from reporting it's version number. Advertising what version you are running isn't the best idea from a security standpoint, we want to at least make the hackers work for it.

There are a few other tweaks which I use. They mostly relate to my particular usage, so may or may not be appropriate for you. For example:

client_max_body_size  4096k;
client_header_timeout 10;
client_body_timeout   10;
keepalive_timeout     10 10; 
send_timeout          10;
server_names_hash_bucket_size 64;

client_max_body_size tweaks the maximum size of the client request body (which can restrict things like file uploads), I've increased it to 4MB from the default of 1MB.

The *_timeout settings make sure the server doesn't timeout when we don't want it to, and does when we do. You can find more information on the specifics of each setting in the nginx documentation. In this case I have decreased the default timeouts to make the site less vulnerable to distributed denial of service attacks (DDoS), by limiting the amount of time nginx waits for slow requests.

Finally server_names_hash_bucket_size is increased to 64 simply because I have an unusually long server name.

At this point it's probably wise to make sure everything is still working as expected, by reloading nginx:

$ systemctl reload nginx

Add your site(s)

nginx is now ready for us to start adding sites. As things stand nginx will only serve static html (Though once properly configured, nginx will serve things like php and/or whatever else you may wish). Our html lives in /var/www/ (for example /var/www/mysite/) and you will find /var/www/html/ already hosts the default nginx site, which contains a simple html page.

However nice it may be to tinker around with the default site, you will want to start setting up your own at some point. To do that the important thing to realise is that you need to tell nginx where to find your site's files and what to do with them. Therefore you need to start editing /etc/nginx/sites-enabled/ and /etc/nginx/sites-availible/. You will find the default site's configuration files /etc/nginx/sites-enabled/default and /etc/nginx/sites-availible/default are already populated, where the former is a symbolic link pointing to the latter.

To quickly summarise the difference between to two locations: sites-enabled contains your currently active sites, whereas sites-availible contains all of your available sites, which are activated by symlinking them from sites-enabled. For example:

$ ln -s /etc/nginx/sites-available/mysite /etc/nginx/sites-enabled/mysite

would enable mysite (assuming you have a mysite configuration in sites-availible), and:

$ rm /etc/nginx/sites-enabled/default

would disable the default site. The default site includes pretty much everything you need to get going, if you have a look in the file:

$ nano /etc/nginx/sites-available/default

You will see a bunch of stuff about servers, locations etc. and it will look a little something like this:

server {
        listen 80 default_server;
        listen [::]:80 default_server;

        root /var/www/html;

        # Add index.php to the list if you are using PHP
        index index.html index.htm index.nginx-debian.html;

        server_name _;

        location / {
                # First attempt to serve request as file, then
                # as directory, then fall back to displaying a 404.
                try_files $uri $uri/ =404;

Let's take a closer look at the contents of that file:

listen 80 default_server;
listen [::]:80 default_server;

This tells nginx to listen on port 80 (the default http port), these two lines cover IPv4 and IPv6 respectively.

root /var/www/html;

This tells nginx where the root of your site is, in other words: Where your site's html files are.

index index.html index.htm index.nginx-debian.html;

The index directive tells nginx what files will be used as an index, they are checked in the order specified i.e. index.html will be checked before index.htm.

server_name _;

This is where you'd specify your domain if using your own, in my case that's:

server_name megalomaniacslair.co.uk www.megalomaniacslair.co.uk;

You can also use wildcards and regular expressions. However, you should use exact names where possible, see the nginx documentation for more information on why that's the case.

location / { 
    # First attempt to serve request as file, then
    # as directory, then fall back to displaying a 404.
    try_files $uri $uri/ =404;

The location blocks tell nginx how to process the request URI (the part of the request that comes after the domain name or IP address/port). The default setup simply serves the files it finds and displays a 404 if it finds none to serve.

If all you want is to serve basic HTML via HTTP then that's pretty much all there is to it. Simply copy and adjust for however many sites you have, start adding your HTML into /var/www/yoursite and you'll be serving your sites with nginx in no time.

However, serving basic HTML pages via HTTP is only the staring point for us. When you're hosting webpages on the big bad Internet you need to consider things like security and privacy, you may also need to serve dynamic content (e.g. PHP, Node.js, etc.). Tune in next time when I'll be looking at securing your connections with encrypted HTTPS.

Next time: (Web) Serving Raspberry Pi: Part 3 - HTTPS with Let's Encrypt