Deploying Let’s Encrypt certificates using tls-alpn-01 (https)
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)
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
Make sure you make./dehydrated
executable:
chmod +x dehydrated
You will also need a config file and a domains.txt:
wget https://raw.githubusercontent.com/lukas2511/dehydrated/master/docs/examples/configwget 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
(simply comment out everything else and add your domain)
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 mkdir /var/www/dehydrated
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;
Make sure the fullchain.pem
file and privkey.pem
file match the ones in your /etch/nginx/certs/mydomain.com/
folder. You might need to do some renaming.
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