Skip to content

How to use Let's Encrypt certificates to secure Nginx’s SSL configuration

Estimated time to read: 6 minutes

This tutorial will guide you through setting up a nginx web server secured by a free certificate from letsencrypt.

Prerequisites

  • a running instance
    • if you don’t already have one, you can follow the first step using cloud-init below.
  • You’ll also need to assign a floating IP to your instance. And making sure that the desired domain name points to the floating IP.

Step 1: Installing packages

Using cloud-init

If you don't have an instance running yet, go to the Fuga dashboard select compute, then instance and click on Create Instance. Select below Boot Source, a distribution, in this tutorial Ubuntu 20.04 LST is being used.

Select a Boot disk size, flavor, network, key pair, security group, and give your instance a name.

Unfold the Advanced Settings and deploy the script below:

#cloud-config
package_update: true
package_upgrade: true
packages:
 - nginx
 - letsencrypt

Without using cloud-init

If you already have an instance running that you want to use, you can install the packages by running the following command:

sudo apt update
sudo apt upgrade
sudo apt install nginx letsencrypt

Step 2: Configuring nginx

The first part of this is creating the directory for your website. For example:

sudo mkdir -p /var/www/example.com/html

Configuring nginx is pretty simple as the syntax is easy to understand. To start, remove the default configuration files. You can also choose to keep these files and use it as a default virtual host to keep a placeholder page.

sudo rm /etc/nginx/sites-enabled/default
sudo rm /etc/nginx/sites-available/default

We recommend keeping every site in its own configuration file, so now we'll create a new file. Try to keep the filename in line with your website's main domain name:

sudo editor /etc/nginx/sites-available/example.com.conf

Place the following configuration in your websites' configuration file. Change all occurrences of example.com with your domain name:

server {
        listen 80;
        listen [::]:80;
        root /var/www/example.com/html;

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

    server_name example.com;
    access_log /var/log/nginx/example.com.access.log;
    error_log /var/log/nginx/example.com.error.log;
}

Next, we need to enable the configuration. This is easily done with a symlink:

ln -s /etc/nginx/sites-available/example.com.conf /etc/nginx/sites-enabled

Now we'll test the configuration for syntax errors and, if none are detected, reload nginx:

sudo nginx -t && sudo nginx -s reload

Now nginx is ready to serve normal HTTP requests. We'll need this for letsencrypt to work.

Step 3: Getting the certificate from lets encrypt

Letsencrypt's client, certbot, has a nginx installation plugin but according to the developers it is still considered experimental and buggy. We'll therefore choose for the manual installation method. First, we'll need to request the certificate. With most CA's this means generating a private key and CSR and submitting the CSR through their website and paying them for the certificate. Letsencrypt automates this entire process and also submits free certificates. To request a certificate for manual installation execute the following command (replace example.com with your domain name).

Note, you can use multiple -d arguments to get multiple domain names in the same certificate.

letsencrypt certonly -a webroot --webroot-path=/var/www/example.com/html/ -d example.com -d www.example.com

The letsencrypt client will now ask you to enter your e-mail address and to accept the terms of usage.

The letsencrypt client will now generate a private key and CSR, request a certificate from the CA, validate that it has control of the domain and finally download the certificate and put it on your system. The following files will now be placed on your instance:

    /etc/letsencrypt/live/example.com/cert.pem - The signed certificate
    /etc/letsencrypt/live/example.com/chain.pem - The certificate chain (root certificate and all intermediate certificates) needed to validate the certificate is signed by a trusted root certificate.
    /etc/letsencrypt/live/example.com/fullchain.pem - A combination of both cert.pem and fullchain.pem.
    /etc/letsencrypt/live/example.com/privkey.pem - The private key. Keep this file private and secure at all times. Compromise of this file (for example, emailing this file, downloaded by a third party etc.) can allow third parties to hijack your SSL certificate.

Step 4: Setting up nginx to use the new certificate

An SSL Configuration can be as simple as this:

        listen 443 ssl;
        ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

However, this simple configuration means a few weaknesses will be present. You can use Qualys's SSL Test to test your configuration and find the most common weaknesses. At the time of this writing this simple SSL configuration will get you a "B" from this test. Of course, we want to do better than that. This test also still allows your website to be viewed unencrypted. Let’s fix this.

Step 5: Setting up HTTP to HTTPS redirect

This step is optional but does mean your website will be more secure and allow your users to automatically go to your SSL website.

First, open your nginx configuration file:

sudo editor /etc/nginx/sites-available/example.com.conf

Remove any SSL entries in your server configuration block and add the following settings:

        return         301 https://$server_name$request_uri;

This will redirect all normal HTTP traffic to the HTTPS counterpart. Next, we'll need to actually setup the HTTPS server. We'll also harden the SSL security configuration. Copy the following code into your configuration file (replace example.com with your own domain):

server {
        listen 443 ssl;
        ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
        ssl_protocols TLSv1.2;
    ssl_ciphers EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
    ssl_prefer_server_ciphers On;
    ssl_session_cache shared:SSL:128m;
    add_header Strict-Transport-Security "max-age=31557600; includeSubDomains";
    ssl_stapling on;
    ssl_stapling_verify on;
    # Your favorite resolver may be used instead of the Google one below
    resolver 8.8.8.8;
    root /var/www/example.com/html;

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

    server_name example.com;

    location / {
            try_files $uri $uri/ =404;
    }


    location ~ /\.ht {
            deny all;
    }
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;
}

Please note that this configuration excludes some older OSs and browsers from connecting. Most notably IE 6 to 10 on Windows XP, Vista or Windows 7, Safari 5, 6 on Mac OSX 10.6 and 10.8 and Android until version 4.3.

To enable all these clients (with the only exception being IE6 on windows XP), we'll need to allow TLSv1. To do this, replace the ssl_protocols in the above config with this line:

ssl_protocols TLSv1 TLSv1.2;
This configuration will get you at least an A on SSL Labs.

Step 6: Header hardening

Besides the SSL configuration hardening, we can also add some HTTP Headers that further enhance the security of your website. Below are some recommended headers. To read more about these headers and what options you can use and which are applicable to you, please visit https://scotthelme.co.uk/hardening-your-http-response-headers/

add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Xss-Protection "1";
add_header Content-Security-Policy "default-src 'self'";

Step 7: Setting up automatic renewal

Since letsencrypt only issues certificates valid for 90 days, automatic renewal is important. To do so, we'll create a renewal script, using letsencrypt's renew command. This command automatically renews all certificates with a validity of less than 30 days.

Save the following script in a logical location. We'll use /opt/renewCerts.sh

#!/bin/sh
# This script renews all the Let's Encrypt certificates with a validity < 30 days
if ! letsencrypt renew > /var/log/letsencrypt/renew.log 2>&1 ; then
    echo Automated renewal failed:
    cat /var/log/letsencrypt/renew.log
    exit 1
fi
nginx -t && nginx -s reload

Now we need to make sure it’s owned by root and executable by root:

chown root.root /opt/renewCerts.sh
chmod u+x /opt/renewCerts.sh

Next, we'll add it to cron for automatic execution:

sudo crontab -e

In our example we'll run it once a week, which is more than enough to renew the certificates before they expire:

@weekly /opt/renewCerts.sh