Ubuntu 18.04 server: nginx web server + Let's Encrypt

🍪 7 min read

In this note I describe my basic nginx Webserver setup on Ubuntu 18.04 in combination with Let’s Encrypt certificates to support encrypted SSL connections. If you need an introduction to setting up a server for the first time, take a look at my note about a basic Ubuntu 18.04 server setup. This note is directly based on the described basic setup, but it should work the same on every other Ubuntu 18.04 installation. I’ll use my domain dennisnotes.com in the examples, just change it to the domain you want to use.

Installation

nginx is included in the Ubuntu apt repository and can be easily installed:

sudo apt install nginx

After installing we should create a basic website to get everything up and running. First of all create a new, simple http file, which we’ll server over nginx later, to test our setup.

sudo mkdir /var/www/dennisnotes.com
sudo vim /var/www/dennisnotes.com/index.html

Add this little example site code to this new file. It just outputs nothing, so nothing fancy here.

<!DOCTYPE html>
<html>
    <body>
            nothing
    </body>
</html>

Afterwards we need to create a nginx configuration file, which will be used as the default_server, when somebody accesses the server via HTTP. To do so create a new configuration file as follows:

sudo vim /etc/nginx/sites-available/dennisnotes.com.conf

The content of this file is pretty simple to understand. We listen on port 80 for IPv4 as well as IPv6 and use this configuration as our default_server, which means, that all domains, which point to our servers IP, but don’t have a configuration server block, will use this server block. Under server_name we add the domains for which the block is valid, as well as the newly created folder, which contains the index.html as root directory. We define index.html as our index, which means, that this file will be served, when someone accesses the URL without a specific file/directory parameter. If someone calls a URL for which there is no corresponding file, a 404 error is returned.

server {
    listen 80 default_server;
    listen [::]:80 default_server ipv6only=on;

    server_name dennisnotes.com www.dennisnotes.com;
    root /var/www/dennisnotes.com;

    index index.html;
    location / {
            try_files $uri $uri/ =404;
    }
}

After you’ve created this configuration file, we need to enable it. To do this, the nginx service must be stopped and a symbolic link, which points to the configuration file just created, must be added in /etc/nginx/sites-enabled. We’ll also remove the default configuration, which will be replaced by our new config. Then the nginx server is restarted and the status checked.

sudo systemctl stop nginx
sudo rm /etc/nginx/sites-enabled/default
sudo ln -s /etc/nginx/sites-available/dennisnotes.com.conf /etc/nginx/sites-enabled/dennisnotes.com.conf
sudo nginx -t
sudo systemctl start nginx
sudo systemctl status nginx

After installing we need to enable the ports 80/tcp (HTTP) and 443/tcp (HTTPS) in our firewall. If you are not using UFW, enable this two ports in your firewall if you use one - you should! If you use UFW (you do if you followed my basic Ubuntu 18.04 server setup note) you can enable the required ports as follows:

sudo ufw allow 'Nginx Full'

Then the nginx server is restarted and the status checked. Now a call to http://dennisnotes.com in the browser - or curl http://dennisnotes.com - should return nothing.

Setup HTTPS with a Let’s Encrypt certificate

Now we have a web server that delivers content over HTTP. I want all of my traffic - even on static sites like this one, to which none of your data is transferred - to be delivered via a encrypted connection. Thanks to Let’s Encrypt it is easy to get a free certificate for your own domain. We’ll just add the certbot ppa to get a current version of certbot and install it. Afterwards we’ll issue a certificate for our domain.

sudo add-apt-repository ppa:certbot/certbot
sudo apt install python-certbot-nginx
sudo certbot --rsa-key-size 4096 --nginx (Add Email, Agree, set https for all domains, redirect http to https)

In the last step certbot will ask you for an email address, on which they would provide you information about expiring certificates - which shouldn’t happen with our setup - and so on. You can choose for which domains you want a certificate, in our case this would be dennisnotes.com as well as www.dennisnotes.com. It also asks if you want to redirect http to https, you can agree to this, but we’ll change the created configuration anyways. Certbot will then automatically issue a certificate for both domains and adjust the NGINX configuration. After installation, the website should then be accessible via https://dennisnotes.com with a valid certificate. A cronjob is also automatically created, which renews the certificate regularly, so no manual intervention should be necessary.

SSL configuration for an A+ SSL rating

We’ll create a more reusable configuration file, which will lead to an A+ score on SSL Labs. First, we outsource SSL options that can normally be used by all websites. Before we create the configuration file, we need to create a Diffie-Hellman (DH) group:

sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096

This step will take a while. After it finished we create a new SSL configuration file as a nginx snippet:

sudo vim /etc/nginx/snippets/ssl.conf

Here’s the content of my file. I use the ssl_ciphers recommended by Mozilla for compatibility, which result in a 90% cipher strength. You could use the recommendations from cipher.li for a 100% score, but some older devices won’t be able to connect to the serve, because they don’t support the given ciphers.

# Hide the nginx version
server_tokens off;
# Disable gzip due the HTTPS BREACH attack. Only activate if it is really required by your application.
gzip off;

# SSL Settings
# The generated DH Group
ssl_dhparam /etc/ssl/certs/dhparam.pem;

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ecdh_curve secp384r1;
ssl_prefer_server_ciphers on;

# Ciphersuites recommendation from the chiper.li
# Use this chipersuites to get 100 points of the SSLabs test
# Some device will not support
# ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384";

# Mozilla Ciphersuits Recommendation
# Use this for all devices supports
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';

ssl_session_timeout 10m;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;

# OSCP stapling
ssl_stapling on;
ssl_stapling_verify on;

I create a separate header.conf file in /etc/nginx/snippets/header.conf as follows:

# HSTS Header, only use HTTPS, applied on all subdomains
add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;";
add_header X-Robots-Tag none;
# Disable mime content-type sniffing
add_header X-Download-Options noopen;
add_header X-Permitted-Cross-Domain-Policies none;
add_header X-Content-Type-Options "nosniff" always;
# Deny your page to be embedded in frames / iframes to protect from clickjacking
add_header X-Frame-Options DENY;
# Enable Cross-Site scripting protection
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "no-referrer" always;
add_header Feature-Policy "geolocation 'self'";

After the SSL snippet is created we need to update the actual websites configuration file:

sudo vim /etc/nginx/sites-available/dennisnotes.com.conf

The file was edited by certbot during the certificate installation. We delete the automatically generated SSL part and replace it by including the SSL snippet. We also set parameter ssl_trusted_certificate, for OSCP stapling, to the chain.pem created by certbot.

server {

        server_name dennisnotes.com www.dennisnotes.com;
        root /var/www/dennisnotes.com;

        index index.html;
        location / {
            include /etc/nginx/snippets/header.conf;
            try_files $uri $uri/ =404;
        }

    listen [::]:443 ssl http2 ipv6only=on; # managed by Certbot
    listen 443 ssl http2; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/dennisnotes.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/dennisnotes.com/privkey.pem; # managed by Certbot

    ssl_trusted_certificate /etc/letsencrypt/live/dennisnotes.com/chain.pem;
    include /etc/nginx/snippets/header.conf;
    include /etc/nginx/snippets/ssl.conf;
}

server {
    if ($host = www.dennisnotes.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    if ($host = dennisnotes.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot

    listen 80 default_server;
    listen [::]:80 default_server ipv6only=on;

    server_name dennisnotes.com www.dennisnotes.com;
    return 404; # managed by Certbot
}

After saving the configuration restart nginx, after you’ve tested the configuration for errors.

sudo nginx -t
sudo systemctl restart nginx

Now you can test the configuration on the SSL Labs website. You should get an A+ rating. This completes the basic nginx setup. Further pages can be added here according to the example and provided with an SSL certificate.