Deploying Let’s Encrypt certificates using tls-alpn-01 (https)

Some stock image to make this post more attractive (I hope 😜)

For those who don’t know what Let’s Encrypt is: it’s a service that lets you generate free certificates so you can run your website with https.

How does it work? You run the certbot tool from Let’s Encrypt on your Linux server, pick the domain name you want a certificate for, certbot verifies the domain is yours and generates the certificate for you. Let’s Encrypt provides you with 3 options to verify the domain is yours:

- Posting a specified file in a specified location on a web site (the HTTP-01 challenge)
- Offering a specified temporary certificate on a web site (the TLS-SNI-01 challenge)
- Posting a specified DNS record in the domain name system (the DNS-01 challenge)
source

As I was working on a server with only port 443 (https) open to the world, I had to use the tls-sni-01 challenge. However, since January 2018, Let’s Encrypt disabled tls-sni-01 as it could be used to request certificates for domains you don’t own.

That left me with http-01 and dns-01. As port 80 (http) was blocked and I didn’t have control over dns, I had to find another option.

tls-alpn-01

After they abandoned tls-sni-01, work started on a new way to verify your domain using a https challenge: tls-alpn-01. This challenge works by creating specially crafted certificates just for the purpose of the verification. Also known als ALPN certificates.

As I was used to certbot, I thought I could just do this:

sudo certbot --preferred-challenges tls-alpn-01

Unfortunately, certbot responded with

None of the preferred challenges are supported by the selected plugin

😞

At the time of writing tls-alpn-01 hasn’t been implemented in Let’s Encrypt’s certbot.

dehydrated.io

It turns out that this domain verification protocol is actually defined by ACME and that certbot is just an ACME client. In fact, next to certbot there are lots of other ACME clients you can use. You’ll find a list on the Let’s Encrypt website: https://letsencrypt.org/docs/client-options/

One of them is dehydrated.io:

Dehydrated.io is an ACME client completely written in bash, so it works on Linux out-of-the-box. One of the latests commits is support for tls-alpn-01.

Request a certificate using tls-alpn-01 and dehydrated.io

Start by getting the latest version of dehydrated:

wget https://raw.githubusercontent.com/lukas2511/dehydrated/master/dehydrated

You will also need a config file and a domains.txt:

wget https://raw.githubusercontent.com/lukas2511/dehydrated/master/docs/examples/config
wget https://raw.githubusercontent.com/lukas2511/dehydrated/master/docs/examples/domains.txt

You’re domains.txt should contain a list with the domains you want a certificate for, for example:

my-awesome-domain.com

The config file needs little editing:

  • CHALLENGETYPE: Specify tls-alpn-01.
  • ALPNCERTDIR: Set the location for your ALPN certificates (used by the verification process).
  • CERTDIR: Set your desired certification location. I chose to put them in a subfolder of nginx as I will be using nginx to serve my website.
CHALLENGETYPE=”tls-alpn-01"
ALPNCERTDIR=”/etc/dehydrated/alpn-certs”
CERTDIR=”/etc/nginx/certs”

You will also need an ALPN responder: a tiny https server that serves those ALPN certificates just for the verification process. You can copy-paste the example responder from the documentation: https://github.com/lukas2511/dehydrated/blob/master/docs/tls-alpn.md. Name it alpn-responder.py

We’re not going to use a nginx load balancer as described in the documentation so make sure you change

HOST, PORT = "0.0.0.0", 10443

to

HOST, PORT = "0.0.0.0", 443

This way, the responder runs on port 443

Open a second terminal and run

sudo python3 alpn-responder.py

You can check if the responder is running on port 443 with:

sudo netstat -tulpn | grep 443

Now for requesting your certificate using the tls-alpn-01 verification process, first run

./dehydrated --register --accept-terms

Then run dehydrated with the config file you edited earlier:

sudo ./dehydrated -c -f config

If all goes well, you should now have your certificates in “CERTDIR” you specified in the config file. In my case, that was ”/etc/nginx/certs”.

Configure Nginx to use the certificates

As for configuring Nginx, create (or edit) a .conf file and add in your certificates:

server {
listen 443 ssl;
server_name my-awesome-domain.com;
ssl_certificate /etc/nginx/certs/mydomain.com/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/mydomain.com/privkey.pem;

Renewing certificates

If you want to renew your certificates, you will have to stop nginx with

sudo systemctl stop nginx

Start the responder with

sudo python3 alpn-responder.py

In another terminal, request your new certificates with:

sudo ./dehydrated -c -f config

Kill your responder and restart nginx:

sudo systemctl start nginx

This might be a problem in production as you have to stop your nginx server temporarily. There should be a way to solve this by running a load balancer who then decides where to send the traffic to.

Sam