Nginx + SSL/TLS through Let's Encrypt

Let’s Encrypt + Nginx is simple easy!

Look, it’s 2020, and if your site isn’t provided under SSL/TLS then you are behind even the least sophisticated scammers out there.

Here is a quick walk through on using certbot from Let’s Encrypt, that provides easy to acquire, and even renew your certificates if you’re using Nginx.

NOTE: These directions are geared toward Fedora users, but this is literally just as easy on Ubuntu, Arch, Gentoo, etc.

Direct DNS

One way Let’s Encrypt verifies you are the owner of a domain/site is they issue a challenge. To do this, Let’s Encrypt calls the domain submitted and looks for a specific answer. To ensure this works, you need to register your domain, and have the domain directed to your server. I host my server and DNS on DigitalOcean.com. My DNS record for this is

A example.utahcon.dev 159.203.86.63 300
AAAA example.utahcon.dev 2604:a880:800:a1::118:1001 300

Install the packages

Of course you need nginx and certbot installed for this to work. Additionally you need to include the python-certbot which depending on which version of python you run on your server, you’ll need either python2-certbot or python3-certbot. I am running python 3.x.

$ sudo dnf install nginx certbot python3-certbot

Create static page

in /var/www/html/index.html or any other directory you really want to use.

<!DOCTYPE html>
<html>
        <head>
                <title>example.utahcon.dev -- secured<title>
        </head>
        <body>
                <p>example.utahcon.dev -- secured</p>
        </body>
</html>

Configure nginx

Now we tell nginx where we want it to find this file, and how to serve it. Fedora keeps things nice and clean so we will create a file under /etc/nginx/conf.d/ called example.utahcon.dev.conf. Literally you can name your file anything, as long as it ends in .conf.

First, let’s start by telling nginx where to listen for requests for our page:

listen 80;
listen [::]:80;
listen 443 ssl;

We also want to tell nginx the name of our server (incase we decide to host multiple sites on this server):

server_name example.utahcon.dev;

Now, when calls come in, how should nginx handle them? We want it to simple serve up the static files in /var/www/html, so we tell it the root, and we want it to automatically serve the index.html page if no other page is requested:

root /var/www/html;
index index.html;

When we put all this together in the server declaration for nginx we get this:

server {
        listen 80;
        listen [::]]:80;
        listen 443 ssl;

        server_name example.utahcon.dev;
        
        root /var/www/html;
        index index.html;
}

Get a cert

So the power of certbot is that we can use it to call Let’s Encrypt, and make a request for a certificate, accept a challenge, and return a challenge answer.

Additionally, it will update the nginx configuration for us!

Simply run:

$ sudo cerbot --nginx -d example.utahcon.dev

Your output might vary slightly, but you should see something like this:

Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator nginx, Installer nginx
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for example.utahcon.dev
nginx: [warn] could not build optimal types_hash, you should increase either types_hash_max_size: 2048 or types_hash_bucket_size: 64; ignoring types_hash_bucket_size
Waiting for verification...
Cleaning up challenges
nginx: [warn] could not build optimal types_hash, you should increase either types_hash_max_size: 2048 or types_hash_bucket_size: 64; ignoring types_hash_bucket_size
Deploying Certificate to VirtualHost /etc/nginx/conf.d/example.utahcon.dev.conf
nginx: [warn] could not build optimal types_hash, you should increase either types_hash_max_size: 2048 or types_hash_bucket_size: 64; ignoring types_hash_bucket_size

Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: No redirect - Make no further changes to the webserver configuration.
2: Redirect - Make all requests redirect to secure HTTPS access. Choose this for
new sites, or if you're confident your site works on HTTPS. You can undo this
change by editing your web server's configuration.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate number [1-2] then [enter] (press 'c' to cancel): 2

Here certbot is asking if we want to add a redirect, forcing traffic from port 80 to port 443. This is strongly suggested BTW.

Redirecting all traffic on port 80 to ssl in /etc/nginx/conf.d/example.utahcon.dev.conf
nginx: [warn] could not build optimal types_hash, you should increase either types_hash_max_size: 2048 or types_hash_bucket_size: 64; ignoring types_hash_bucket_size

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Congratulations! You have successfully enabled https://example.utahcon.dev

You should test your configuration at:
https://www.ssllabs.com/ssltest/analyze.html?d=example.utahcon.dev
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/example.utahcon.dev/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/example.utahcon.dev/privkey.pem
   Your cert will expire on 2020-07-12. To obtain a new or tweaked
   version of this certificate in the future, simply run certbot again
   with the "certonly" option. 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

You can see that certbot was successful in obtaining a key, and placing it where it needs to be, and if we look at our configuration file now it will have changed slightly:

server {
	listen 443 ssl;

	server_name example.utahcon.dev;

	root /var/www/html;
	index index.html;

	if ( $scheme != "https" ){
		return 301 https://$host$request_uri;
	}

    ssl_certificate /etc/letsencrypt/live/example.utahcon.dev/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/example.utahcon.dev/privkey.pem; # managed by Certbot

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

	listen 80;
	listen [::]:80;

	server_name example.utahcon.dev;
    return 404; # managed by Certbot
}

Now if you hit up your example.utahcon.dev you’ll get a working page that is forced through SSL\TLS.

Renewing Certs

Let’s Encrypt certificates have short shelf lives, just 90 days. This limits damage from compromised keys, or mis-issuance. It also encourages automation because humans hate having to do things every 90-days (like changing passwords or SSL keys).

However, the good folks at Let’s Encrypt built this into certbot too! We simply need to tell certbot to run periodically to check for renewals. So we are going to add the following to /etc/crontab

0 0 * * * root /usr/bin/certbot renew --post-hook "systemctl reload nginx"

This runs certbot, which checks renewals, and if needed updates keys, and then reload nginx.

That’s it!

Unless you ran into issues with SELinux, or firewalls, you should be all setup and running now. Let me know if you have questions, or if there is something here that isn’t accurate. Happy hosting!