How to setup the Nginx web server on Ubuntu 18.04 Bionic Beaver Linux

Objective

Learn how to install and configure the Nginx web server on Ubuntu 18.04 Bionic Beaver

Requirements

  • Root permissions

Conventions

  • # – requires given linux commands to be executed with root privileges either
    directly as a root user or by use of sudo command
  • $ – requires given linux commands to be executed as a regular non-privileged user

Other Versions of this Tutorial

Ubuntu 20.04 (Focal Fossa)

Introduction

nginx-logo

The Nginx web server, together with Apache, is one of the most known and used web servers in the world. It is generally less resource-hungry than Apache, and can be also used as a reverse-proxy.

In this tutorial we will see how to install and configure the Nginx web server on Ubuntu 18.04 Bionic Beaver.

Step 1 – Installation

Installing Nginx on Ubuntu 18.04 is very easy, we just need to use apt-get:

$ sudo apt-get update && sudo apt-get install nginx

The first command synchronizes our machine with ubuntu repositories, while the second actually installs the nginx package. Few seconds and the server will be installed on our system. The installation scripts will also take care of starting the nginx service.

We can easily verify that the service is running using the following linux command:

$ sudo systemctl is-active nginx

The command above will return active if the service is up: indeed, if we point the browser to the server address, or to localhost if we are operating from the machine itself, we should visualize the nginx welcome page:

Nginx welcome page

Nginx welcome page


Step 2 – Firewall setup

To make our server being able to serve pages to other machines we must setup the firewall to allow incoming traffic through port 80 (the default), and port 443 if we want to use the https protocol. The exact command to run to accomplish that, depends on the firewall manager in use on the machine, but here I will assume the ufw is running, since it is the default on Ubuntu.

First, we verify that the firewall is active:

$ sudo ufw status

If it’s not you can activate it by executing the following linux command:

$ sudo ufw enable

However be careful when, because as the system will notify you, activating the firewall could destroy currently existing connections. To allow incoming connections via port 80, we should run:

$ sudo ufw allow 80/tcp

To allow port 443, instead:

$ sudo ufw allow 443/tcp

Finally, to visualize the current status of the firewall, we can run:

$ sudo ufw status numbered
Status: active

     To                         Action      From
     --                         ------      ----
[ 1] 443/tcp                    ALLOW IN    Anywhere
[ 2] 80/tcp                     ALLOW IN    Anywhere
[ 3] 443/tcp (v6)               ALLOW IN    Anywhere (v6)
[ 4] 80/tcp (v6)                ALLOW IN    Anywhere (v6)

As you can see, the command above will give us an overview of the configured rules, indexed by number.

Nginx server blocks (Virtual Hosts)

Nginx server blocks, are the equivalent of Apache VirtualHosts, and are used to run more than one site on the same server machine. On a standard installation of Nginx, we can find the default server block is /etc/nginx/sites-available/default. Let’s take a look at it:

# Default server configuration
#
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;
  }

  [...]

}

The one above is a streamlined version (I just removed comments) of the default Nginx server block on Ubuntu 18.04. As you can see, each directive ends with a semicolon. The first thing we see inside the Server section, on Lines 4-5, are the listen directives. The first one is for ipv4 while the second for ipv6. Actually this could be shortened as listen [::]:80 ipv6only=off.

The default_server directive sets this server block as the default one, meaning that it will be used if no other configurations match a requested name. This directive can be used only on one server block at a time.

The root directive on Line 8 sets the path to the root directory for the site that will be served by the block: it’s basically the equivalent of Apache’s DocumentRoot.

The index directive on line 11 defines the files that can be used as index. The files will be checked in order.

On Line 13, the server_name directive is used to define the server name to be assigned to the configuration, and determines the server block that will handle the request. When defining the server name, it’s possible to use wildcards and regular expressions. In this case, the value provided is _: this is used because is an invalid value, and will never match any real hostname (remember that this configuration is a catch-all).

Finally, we have the location directive on Line 15: it changes the way a request is handled within the server block. In this case, the path to be matched for the instructions to take place, is /. The part of the uri to be matched is the one after the host segment.

Inside the location “stanza”, at Line 18 we can observe another directive, try_files: it checks the existence of files in the specified order, using the first found to fulfill the request. In this case, as suggested from the comment in the section, it first tries to match a file, than a directory. If nothing satisfies the request, a 404 page will be displayed to the user. Notice that the request is represented as the $uri variable, and what defines it as a directory is the trailing slash.



Defining a custom server block

We should now create a custom server block to serve an html site. As a first thing, we will create the directory that will serve as document root for the block, let’s call it example:

$ sudo mkdir /var/www/example

We also need to create an index.html page to be displayed when we reach the site:

$ echo "Welcome to example!" | sudo tee /var/www/example/index.html > /dev/null

Once it is done, we can create a server block in the /etc/nginx/sites-available directory, for consistency, we will name it “example”:

server {
    listen 80;
    root /var/www/example;
    index index.html;
    server_name www.example.lan;
}

To test that our configuration is correct and doesn’t contain any syntax error, we can run the following linux command:

$ sudo nginx -t

Now, since we don’t have a dns server in place, to send a request to our server with the specified name, we must add an entry in the /etc/hosts file of the client machine. In this case the address of the machine I am using as a server (in a Virtual host environment) is 192.168.122.89, therefore:

# The client /etc/hosts file
[...]
192.168.122.89 www.example.lan

Before we activate our new server block, we have the chance to verify that the default configuration indeed works as a default-catchall. If we now navigate to “www.example.lan” from the client machine where we just added the hosts entry, we can see that the server will respond to our request with the default nginx page (since the new block is not yet activated).

To activate our server block, we must create a symlink from the configuration we wrote in /etc/nginx/sites-available to /etc/nginx/sites-enabled:

$ sudo ln -s /etc/nginx/sites-available/example /etc/nginx/sites-enabled

After that, we need to restart Nginx:

$ sudo systemctl restart nginx

At this point, if we navigate to “www.example.lan”, we should see our not very complicated page:

Example default page

Example default page


Using ssl

To use ssl we basically have two options: obtaining a certificate from a certificate authority, or use a self-signed certificate. In our first example we are going to generate a certificate on our own. Run the following linux command to proceed:

$ sudo openssl req -x509 \
  -days 365 \
  -sha256 \
  -newkey rsa:2048 \
  -nodes \
  -keyout /etc/ssl/private/example.key \
  -out /etc/ssl/certs/example-cert.pem

With this command we generated a self signed certificate valid for 365 days, and a 2048 bit rsa key. The certificate and the key will be saved in /etc/ssl/certs/example-cert.pem and /etc/ssl/private/example.key files respectively. Just answer the questions that will be asked, paying particular attention when entering the FQDN: it must match the domain that will use the certificate for it to work correctly.

You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:IT
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:Milan
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Damage Inc.
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:www.example.lan
Email Address []:

Now that we have our certificate and key, we must modify our server block configuration, so that it becomes:

server {
    listen 443 ssl;
    server_name www.example.lan;
    ssl_certificate /etc/ssl/certs/example-cert.pem;
    ssl_certificate_key /etc/ssl/private/example.key;
    root /var/www/example;
    index index.html;
}

As you can see we modified the listen directive at Line 2, using port 443 and also enabling the ssl parameter, then we added two new directives, at Lines 4-5: ssl_certificate and ssl_certificate_key, which points respectively to the certificate and the certificate key location.

After restarting the nginx service, if we now navigate to https://www.example.lan we should see the warning issued by the browser, due to the fact that certificate is self-signed. Nevertheless our configurations is working and the we are using an encrypted connection:

Invalid certificate warning

Invalid certificate warning


Using Let’s encrypt

The alternative to self-signed certificates are certificates issued by a verified third party. While we can buy a certificate from a certificate authority, we also have the option to use “Let’s encrypt!”.

“Let’s encrypt” is itself a free and open certificate authority which lets us automatically obtain a certificate trusted by the browser using the ACME protocol and a certificate management agent which runs on the server. The only condition is being able to demonstrate that we have control over the domain we want to use the certificate for.

To use the service, the first thing to do is to install the certbot ACME client and the nginx-specific plugin:

$ sudo apt-get update && apt-get install certbot python-certbot-nginx

Obtaining a certificate is quite simple:

$ sudo certbot --nginx -m <administrator-email> -d <domain>

Obviously for this to work the domain must point correctly to our publicly accessible server ip. Certbot will prompt us to answer some questions in order to tweak the site configuration, and if all goes well, the certificate and the key will be saved into the /etc/letsencrypt/live/ directory. Certbot will also apply the needed changes to the server block and reload the service.

Conclusions

We installed the Nginx web server on Ubuntu 18.04, saw how to open the needed firewall ports, examined the default Ubuntu server block and created a custom configuration. Finally, we generated a self-signed certificate and implemented the needed modifications to the server block to use the https protocol.

As an alternative we considered implementing “Let’s encrypt!”, which can provide us a recognized certificate with no costs. Don’t hesitate to ask any questions, and visit the official Nginx documentation for more detailed information.