snork.ca ... making kittens cry since 2001
homeabouttoscanaryrssmansvalidatecontact

Debian + NGINX + Let's Encrypt + TLSA 2017-12-01


This post was originally made 2017-12-01 but is being significantly rewritten on 2017-12-15. I just did a renew of my certs and found some easier ways to do this.

Before you start, these instructions are [mostly] written from the perspective of someone logged in as root. If you don't like that, well... that's your problem. These instructions are also written from the perspective of someone who wants both domain.tld and www.domain.tld on the same cert.

I use TLS to encrypt the content of this site between my server and the people reading it. I also use DNSSEC so readers can be sure they are visiting the correct site. Finally I use TLSA so readers know they are using the correct TLS certificate to access my site.

zombiedeer Zombie Deer!

For my TLS I use Let's Encrypt to obtain TLS certificates because I am cheap, and because Let's Encrypt is free. Unfortunately LE certificates are only valid for 90 days and must be renewed about four times as often as typical for-money certs. To make this less-irritating, the certbot application can automatically renew certs. Unfortunately, certbot has given me little more than a headache, especially since each time the cert is renewed it will invalidate my TLSA records. Ah, but there's a better way. 3 1 1 TLSA records use the fingerprint and will be the same if you use the same private key each time you renew the cert. So, how do I get all of this working together to stop being a pain in the ass? Read on...

Step 1: certbot Rots

I seem to keep finding little parts of certbot scattered around my Debian install. I believe that simply removing /etc/cron.d/certbot (or commenting out the command line in it) should disable automatic renewals without removing the application entirely. Then create a new directory to store your certs:

# mkdir /etc/nginx/tlskeys
# cd /etc/nginx/tlskeys

Step 2: The Part With The Keys

If you need to include both domain.tld and www.domain.tld on the same cert, you need to create a config file for openssl like so:

/etc/nginx/tlskeys/domain.tld.openssl.cnf [req]
prompt = no
distinguished_name = req_dn
req_extensions = req_ext

[req_dn]
commonName = domain.tld

[req_ext]
subjectAltName=@alt_names

[alt_names]
DNS.1=domain.tld
DNS.2=www.domain.tld

Next, generate a private key and a CSR for Let's Encrypt. In the second command, the "-config" option tells openssl to use your domain-specific config file. This will ensure that both your www and non-www FQDN's are included in the cert. The third command simply shows you the contents of the CSR that you'll be presenting to Let's Encrypt.

# openssl genrsa -out domain.tld.key 2048
# openssl req -new -key domain.tld.key -config domain.tld.openssl.cnf -out domain.tld.csr
# openssl req -noout -text -in domain.tld.csr

Now use the CSR to get your Let's Encrypt cert.

# certbot certonly --csr domain.tld.csr --webroot -w /var/www/domain.tld -d domain.tld

The "--csr" option tells certbot to use the CSR you manually created, the "--webroot" option tells certbot that you wish to use the webroot plugin to prove you own the domain, the "-w" option tells certbot where to find the root of your site, and the "-d" option tells it which domain to get a cert for. You do not need to use "-d www.domain.tld" here for your www FQDN, because the CSR is doing that for you (by way of the openssl.cnf file you created earlier). When complete, you should hopefully have a few new files saved in your /etc/nginx/tlskeys directory.

  • 0000_cert.pem: Your fancy new cert
  • 0000_chain.pem: The Let's Encrypt chain
  • 0001_chain.pem: The first two files combined

Step 3: Add Your TLSA Record To DNS

If you use BIND as your authoritative DNS server, generate a BIND-ready TLSA record like this (don't forget to replace domain.tld both times in this command):

# printf '_443._tcp.%s. IN TLSA 3 1 1 %s\n' domain.tld $(openssl x509 -in 0001_chain.pem -noout -pubkey | openssl pkey -pubin -outform DER | openssl dgst -sha256 -binary | hexdump -ve '/1 "%02x"')

If you do not use BIND or do not host your own DNS you may have to separate the pieces of the above output and paste them in to whatever order your DNS application/provider requires. If needed, remember to increment your serial. Then re-sign your DNSSEC records for your zone and perhaps force a zone transfer to any slave DNS servers.

Step 4: Use The Cert With NGINX

Begin by renaming the files to something a little more sensible:

# mv 0000_cert.pem domain.tld.pem
# mv 0000_chain.pem domain.tld.chain.pem
# mv 0001_chain.pem domain.tld.fullchain.pem

Then modify your NGINX configs to use the new files:

/etc/nginx/nginx.conf (or wherever you define your virtual hosts) ssl_certificate /etc/nginx/tlskeys/domain.tld.fullchain.pem;
ssl_certificate_key /etc/nginx/tlskeys/domain.tld.key;

Yes there should be a lot more to your SSL setup in your nginx.conf, and I strongly suggest you use your favourite search engine to locate some information about hardening your web server's TLS. Seriously, go do that as soon as you are done here.

When your restart/reload nginx your web site should be running on your fancy new private key, your TLSA should match, and you won't need nearly as much effort to renew your Let's Encrypt cert.

Step 5: Renew Your Cert

When it comes time to renew, all you need to do is re-run the certbot command to collect a new set of .pem files, rename (or link) the .pem files to the filenames used in your NGINX configs, then reload NGINX to use the new cert (no update to DNS for your TLSA records at all).

# cd /etc/nginx/tlskeys
# certbot certonly --csr domain.tld.csr --webroot -w /var/www/domain.tld -d domain.tld
# mv 0000_cert.pem domain.tld.pem
# mv 0000_chain.pem domain.tld.chain.pem
# mv 0001_chain.pem domain.tld.fullchain.pem
# systemctl reload nginx.service

However, it seems to be generally accepted that you should generate a new private key about annually. This means that once a year you'll need to go back through most of these steps, starting with the key/csr generation.

A helpful tip for those who were patient enough to read all the way through this before starting: it might be a good idea (if you can) to set the TTL on your DNS records to a low value the day before you do this. This will minimize any waiting for DNS propagation as well as any damage that might be done if this gets screwed up along the way. Just sayin'. :-)

Made using Notepad++ & FastStone. Hosted on Debian with nginx & php. Powered by North Korean mushrooms.