Secure Nginx with Let's Encrypt on Ubuntu 16.04
Updated on
•6 min read

Let’s Encrypt is a free and open certificate authority developed by the Internet Security Research Group (ISRG). Certificates issued by Let’s Encrypt are trusted by almost all browsers today.
In this tutorial, we’ll provide a step by step instructions about how to secure your Nginx with Let’s Encrypt using the certbot tool on Ubuntu 16.04.
Prerequisites
Make sure that you have met the following prerequisites before continuing with this tutorial:
- You have a domain name pointing to your public server IP. In this tutorial we will use
example.com
. - You have Nginx installed by following How To Install Nginx on Ubuntu 16.04 .
Install Certbot
Certbot is a utility written in python that can automate the tasks for obtaining and renewing Let’s Encrypt SSL certificates and configuring web servers.
First install the software-properties-common
package which provides the add-apt-repository
tool needed for adding additional PPAs.
Update the packages index and install software-properties-common
with:
sudo apt update
sudo apt install software-properties-common
Once the installation is complete, add the certbot PPA repository to your system using the following command:
sudo add-apt-repository ppa:certbot/certbot
Update the packages list and install the certbot package:
sudo apt update
sudo apt install certbot
Generate Strong Dh (Diffie-Hellman) Group
Diffie–Hellman key exchange (DH) is a method of securely exchanging cryptographic keys over an unsecured communication channel. Generate a new set of 2048 bit DH parameters to strengthen the security:
sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048
Obtaining a Let’s Encrypt SSL certificate
To obtain an SSL certificate for our domain we’re going to use the Webroot plugin that works by creating a temporary file for validating the requested domain in the ${webroot-path}/.well-known/acme-challenge
directory. The Let’s Encrypt server makes HTTP requests to the temporary file to validate that the requested domain resolves to the server where certbot runs.
To make it more simple we’re going to map all HTTP requests for .well-known/acme-challenge
to a single directory, /var/lib/letsencrypt
.
The following commands will create the directory and make it writable for the Nginx server.
sudo mkdir -p /var/lib/letsencrypt/.well-known
sudo chgrp www-data /var/lib/letsencrypt
sudo chmod g+s /var/lib/letsencrypt
To avoid duplicating code create the following two snippets which we’re going to include in all our Nginx server block files.
location ^~ /.well-known/acme-challenge/ {
allow all;
root /var/lib/letsencrypt/;
default_type "text/plain";
try_files $uri =404;
}
ssl_dhparam /etc/ssl/certs/dhparam.pem;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS';
ssl_prefer_server_ciphers on;
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 30s;
add_header Strict-Transport-Security "max-age=15768000; includeSubdomains; preload";
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
The snippet above includes the chippers recomendend by Mozilla , enables OCSP Stapling, HTTP Strict Transport Security (HSTS) and enforces few security‑focused HTTP headers.
Once the snippets are created, open the domain server block and include the letsencrypt.conf
snippet as shown below:
server {
listen 80;
server_name example.com www.example.com;
include snippets/letsencrypt.conf;
}
Activate the server block by creating a symbolic link from sites-available
to sites-enabled
:
sudo ln -s /etc/nginx/sites-available/example.com.conf /etc/nginx/sites-enabled/example.com.conf
Reload the Nginx configuration for changes to take effect:
sudo systemctl reload nginx
Run the certbot script with the webroot plugin and obtain the SSL certificate files:
sudo certbot certonly --agree-tos --email [email protected] --webroot -w /var/lib/letsencrypt/ -d example.com -d www.example.com
If the SSL certificate is successfully obtained, certbot will print the following message:
IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at:
/etc/letsencrypt/live/example.com/fullchain.pem
Your key file has been saved at:
/etc/letsencrypt/live/example.com/privkey.pem
Your cert will expire on 2018-04-23. To obtain a new or tweaked
version of this certificate in the future, simply run certbot
again. To non-interactively renew *all* of your certificates, run
"certbot renew"
- If you like Certbot, please consider supporting our work by:
Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
Donating to EFF: https://eff.org/donate-le
Now that we have the certificate files, edit the domain server block as follows:
server {
listen 80;
server_name www.example.com example.com;
include snippets/letsencrypt.conf;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
include snippets/ssl.conf;
include snippets/letsencrypt.conf;
return 301 https://example.com$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
include snippets/ssl.conf;
include snippets/letsencrypt.conf;
# . . . other code
}
With the configuration above we are forcing HTTPS and redirecting the www
version of the domain to the non www
version.
Reload the Nginx service for changes to take effect:
sudo systemctl reload nginx
SSL certificate auto renewal
Let’s Encrypt’s certificates are valid for 90 days. To automatically renew the certificates before they expire, the certbot package creates a cronjob which will run twice a day and will automatically renew any certificate 30 days before its expiration.
Since we are using the certbot webroot plug-in once the certificate is renewed we also have to reload the nginx service. To do so append --renew-hook "systemctl reload nginx"
to the /etc/cron.d/certbot
file so as it looks like this:
0 */12 * * * root test -x /usr/bin/certbot -a \! -d /run/systemd/system && perl -e 'sleep int(rand(3600))' && certbot -q renew --renew-hook "systemctl reload nginx"
To test the renewal process, use the certbot --dry-run
switch:
sudo certbot renew --dry-run
If there are no errors, it means that the renewal process was successful.
Conclusion
In this tutorial, you used the Let’s Encrypt client, certbot to obtain SSL certificates for your domain. You have also created Nginx snippets to avoid code duplication and configured Nginx to use the certificates. At the end of the tutorial you have set up a cronjob for automatic certificate renewal.
If you want to learn more about how to use Certbot, their documentation is a good starting point.