How to Secure Nginx with Let’s Encrypt SSL Certificate

Filed Under: NGINX

Let’s Encrypt is a free, open source and automated certificate authority (CA) pioneered by Internet Security Research Group (ISRG) and offers SSL/TLS certificates to encrypt communications involving two parties.

An SSL certificate in your website will not only increase your website’s security through encryption but also improve your site’s SEO ranking since Google has made it mandatory to include an SSL certificate for a better SEO ranking.

In this article, we will inquire into how to secure an NGINX server with Let’s Encrypt SSL certificate in Ubuntu 18.04.

Prerequisites

  • You have the privileges to open a SSH session in Ubuntu 18.04 server either by using a root user or by using a sudo enabled user.
  • You have already installed NGINX by following our tutorial from here.
  • A registered domain name.
  • DNS A record that points to your server’s IP address based on the domain name that you want to use.
This tutorial requires the use of domain names. Whenever you see either the SUBDOMAIN, DOMAIN, or TLD variables, replace them with your own values.

Install Let’s Encrypt Client library

The very first step that you need to accomplish is to install the Let’s Encrypt client library which in turn will be used to request certificates from LE authority. One way to do this is by cloning the Let’s Encrypt client library from its official repository in your server.

It is also possible to install the same from the official repository of Ubuntu. In Ubuntu’s repository, the client library is known as certbot provided by Let’s Encrypt developers. But we will choose the former method of installing it i.e. by cloning the Github repository of Let’s Encrypt.

To do that, navigate to your chosen installation folder and clone the repository. I have chosen the installation folder as /usr/local, you can choose some other folder as per your environment.


# apt-get install git
# cd /usr/local
# git clone https://github.com/letsencrypt/letsencrypt

Change to the letsencrypt folder, you will find all the cloned folders and files including certbot needed by Let’s encrypt to fetch certificates for you.

List Lets Encrypt Folder

List Lets Encrypt Folder

Configure Let’s Encrypt to verify your domain

In the process of fetching a certificate, the Let’s Encrypt client creates a temporary file in .well-known/acme-challenge folder. This file contains a token to validate the fact that you actually own the domain for which you are trying to fetch the certificates. Remember the said folder falls under the web root of the domain.

Therefore, it is imperative that Let’s Encrypt can access the temporary file to verify the domain. To grant access, you need to configure NGINX since it is the owner of the web root for the domain. Add a location block specifically for the folder /.well-known/acme-challenge that will allow Let’s Encrypt to place a token for domain verification. To do that, Edit the NGINX configuration file for your virtual host. Don’t forget to adjust the root folder as per your environment.



    server {
            listen 80;
            server_name SUBDOMAIN.DOMAIN.TLD;
            root /var/www/html/wordpress;
            ...
            ...
            ...
            location /.well-known/acme-challenge {
               allow all; 
               default_type "text/plain";
            }
           ...
           ...
    }

Verify that the configuration file is syntactically correct and restart NGINX:


# nginx -t 
# systemctl restart nginx

Configure UFW Firewall

Ubuntu 18.04 is shipped with a default firewall manager UFW and if you have already enabled it and is running in your server then no HTTP or HTTPS connections will be allowed to reach the NGINX server.

It is also possible that you are trying to install certificates on a previously configured server and the firewall rules are already present. Of course, you can list the firewall rules using ufw status command in your server before proceeding with enabling HTTP and HTTPS connections in your Ubuntu 18.04 server.

Run the following set of commands in the terminal to allow HTTP and HTTPS connections in your server.


# ufw status
# ufw allow http
# ufw allow https
# ufw reload

Request Let’s Encrypt SSL Certificate

Let us request an SSL certificate for the chosen domain using the Let’s Encrypt client that we cloned previously. Although there are a lot of configuration options available that one can use with Let’s Encrypt script while requesting a certificate, we will use the following options only.

  1. certonly: This option tells the letsencrypt script to obtain the SSL certificate only. We will do the configuration of NGINX manually later.
  2. webroot: This option specifies webroot plugin to be used in the process of validating the domain name.
  3. webroot-path: This option sets the root of the domain and must be pointed to website location that you have configured in the NGINX configuration.
  4. agree-tos: Means agree to the Let’s Encrypt’s terms of service.
  5. email: The email is used to receive expiry notice from Let’s Encrypt but can also be used to recover the lost key.
  6. d: This options specifies the domain name for which certificate will be requested.

Now combining the above options along with letsencrypt client script, request for a certificate using following commands:


# cd /usr/local/letsencrypt
# ./letsencrypt-auto certonly --agree-tos --email your_email@somedomain.com --webroot --webroot-path /var/www/html/wordpress -d SUBDOMAIN.DOMAIN.TLD
Fetched Lets Encrypt Certificate Successfully

Fetched Lets Encrypt Certificate Successfully

The Let’s Encrypt script will show you a small note once the SSL certificates have been fetched successfully and the certificates will get stored in the /etc/letsencrypt/live folder.

Configure NGINX for Let’s Encrypt SSL Certificate

At this stage, the Let’s Encrypt certificate chain has been saved successfully in your server. Now proceed with configuring virtual host file for a secured SSL enabled NGINX web server.

Generate Diffie-Hellman Key

Although using Diffie-Hellman key is optional while configuring Let’s Encrypt certificates with NGINX but it is primarily used as a method of exchanging cryptography keys securely for use in the Let’s encrypt certificate.


# mkdir -p /etc/nginx/ssl
# cd /etc/nginx/ssl
# openssl dhparam -out /etc/nginx/ssl/dhparam.pem 4096

I have placed DH parameter file in the /etc/nginx/ssl directory simply for convenience. You can place the file in some other location.

Configure SSL parameters

Next, add parameters for SSL connection like protocol versions, cipher suites, session, headers that your server should support in a separate file.

This will set up a strong cipher suite that enables Forward Secrecy whenever possible. Further, we will also enable HSTS and HPKP to harden NGINX server for SSL connections. This will enable a strong and future proof SSL configuration to get an A+ grade on the Qualys Labs SSL Test.


# vi /etc/nginx/conf.d/ssl.conf
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:ECDHE-RSA-AES128-GCM-SHA256:AES256+EECDH:DHE-RSA-AES128-GCM-SHA256:AES256+EDH:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
ssl_dhparam /etc/nginx/ssl/dhparam.pem;
server_tokens off;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
#Online Certificate Status Protocol Stapling
ssl_stapling on;
ssl_stapling_verify on;
ssl_session_tickets off;
#HTTP Strict Transport Security
add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";
ssl_trusted_certificate /etc/letsencrypt/live/SUBDOMAIN.DOMAIN.TLD/chain.pem;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
add_header X-Frame-Options DENY;
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options nosniff;
add_header X-Robots-Tag none;

Edit NGINX virtual host

Finally, edit the NGINX server blocks for your chosen domain. Navigate to the default location for NGINX server blocks which is /etc/nginx/sites-available and edit the NGINX virtual host file.


# vi /etc/nginx/sites-available/your_nginx_file.conf

server {
         listen 80;
         listen [::]:80;
         server_name SUBDOMAIN.DOMAIN.TLD;
         return 301 https://$host$request_uri;
}
...
...

The above server block instructs NGINX to listen for any incoming request in the port 80 for the domain SUBDOMAIN.DOMAIN.TLD and redirect the same to the https section, the configuration for which is given below.


...
...
server {
        listen 443 ssl http2;
        listen [::]:443 ssl http2;

        root   /var/www/html/wordpress;
        index index.html index.php index.htm;

        server_name  SUBDOMAIN.DOMAIN.TLD;
        error_log /var/log/nginx/SUBDOMAIN.DOMAIN.TLD-error.log debug;
        access_log /var/log/nginx/SUBDOMAIN.DOMAIN.TLD-access.log;

        ssl_certificate /etc/letsencrypt/live/SUBDOMAIN.DOMAIN.TLD/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/SUBDOMAIN.DOMAIN.TLD/privkey.pem;

        include /etc/nginx/conf.d/ssl.conf;

        location /.well-known/acme-challenge {
               allow all;
               default_type "text/plain";
        }

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

        location = /favicon.ico {
                log_not_found off;
                access_log off;
        }

        location = /robots.txt {
                allow all;
                log_not_found off;
                access_log off;
        }

        location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
                expires max;
                log_not_found off;
        }
}

Check the configuration file for any syntactical error:


# nginx -t 

To activate the server block create a symbolic link of the above virtual host configuration file inside /etc/nginx/sites-enabled folder.


# cd /etc/nginx/sites-enabled
# ln -s ../sites-available/your_nginx_file.conf .

Reload NGINX to apply new settings:


# systemctl reload nginx

To analyze the configuration of above SSL settings, point your browser to SSL Labs Test and type in your domain name and click ‘Submit’. Wait for a minute to view the summary of the test. An A+ rating means, we have included most of the SSL options in the process of securing the NGINX server.

Auto renewal of Let’s Encrypt certificate

The Let’s Encrypt certificates are valid for 90 days. The rationale for this is to limit the damage from key compromise and encourage automation.

To automate the certificate renewal process, create a cronjob using crontab and add a line of script that will execute once every week to renew the certificates when they are about to expire.


# crontab -e
45 4 * * 6 cd /usr/local/letsencrypt/ && ./certbot-auto renew && systemctl restart nginx

The above single line script will run in every Saturday to check if certificates are up for renewal and if it so, it will renew the certificates followed by restarting of NGINX server.

Note that it’s not fully optimized because the NGINX server will get restarted even if the certificate is not renewed. The best option would be to write a custom script to renew the SSL certificate and restart NGINX server if the certificates are actually renewed.

Conclusion

In this article, we learned how to install Let’s Encrypt client library, request a certificate for your domain and how to configure Let’s Encrypt SSL certificates with NGINX. Further, An example cronjob also shows you how to take care of the certificate renewal process regularly.

Comments

  1. Pierre JVV says:

    Brilliant article – just a point of clarification: when modifying the site-specific nginx.conf file, the “include /etc/nginx/ssl/ssl.conf; ” points to file that’s located in /etc/nginx/conf.d/ssl.conf. its an easy fix, but which location is best practice ?

    1. Pankaj says:

      I have fixed it in the article. It’s better to keep the configuration file inside the conf.d directory.

Leave a Reply

Your email address will not be published. Required fields are marked *

close
Generic selectors
Exact matches only
Search in title
Search in content
Search in posts
Search in pages