Let's finally encrypt - HTTPS a decade later
It’s 2025, I just published an article on Advent of Code 2024 in Rust, and my browser won’t even load http://treibgut.net.
Turns out, I’m partying like 2014, with my lighthttpd only configured to serve plain old HTTP on port 80. Ugh.
$SERVER["socket"] == "<ip>:80" {
...
}
Time to do what everyone and their grandma have done and set up HTTPS with Let’s encrypt. Let’s Encrypt was founded in 2015 and is a certificate autority (CA) that hands out free certificates for HTTPS. I think it’s a shiny example of moving internet security forward by turning what was previously an expensive and often proprietary business into a free commodity. And also kudos to Chrome for not even loading my old HTTP website anymore, I’m really overdue moving this to HTTPS.
Giant Magnetic Certificate Retriever
Fetching and renewing certificates from Let’s encrypt happens via an
ACME client (I’m not
the only person immediately thinking of Roadrunner and Coyote here, right?).
Let’s encrypt recommends certbot.
And lo and behold, we already had that set up for another domain on my box,
with the bot started every Sunday via /etc/cron.d/certbot
0 12 * * sun root test -x /usr/bin/certbot -a \! -d /run/systemd/system && perl -e 'sleep int(rand(43200))' && certbot -q renew
The important bit here is certbot -q renew
, the rest just checks files
and directories exist and adds a random delay before running certbot.
NOTE: The random sleep is a nice touch that avoids a load spikes on letsencrypt due to lots of certbots kicking off renewals around the same time.
Requesting a new certificate
The next step was requesting a new certificate for my domain
(I requested both the basename and www.<domain>
):
sudo certbot certonly --webroot
I chose to use --webroot
as I didn’t want to mess with the
already running HTTP server on port 80. That asked for the domains
and the “webroot” of the HTTP server (where certbot wanted to
put some files to allow verifying ownership of the domain).
And with that, I had a shiny certificate for the domain. Given we already had renewal configured (see previous section), the certificate should also be kept up-to-date when it expires.
Configuring lighttpd
Turns out lighttpd requires a combined keyfile
that certbot doesn’t create. So we create a hook to manually merge the files into
the expected format, e.g., in /etc/letsencrypt/renewal-hooks/deploy/create-everything-pem
.
And we can also use that hook to restart lighttpd when a new certificate is
available.
#!/bin/bash
for domain in $RENEWED_DOMAINS; do
if [ "$domain" = "<domain>" ]; then
cd "$RENEWED_LINEAGE"
link=$(readlink cert.pem)
base=${link%%cert??.pem}
suff=${link##*/cert}
cat privkey.pem fullchain.pem > "$base/everything$suff"
ln -fs "$base/everything$suff" everything.pem
fi
done
systemctl restart lighttpd.service
Now that we have the everything.pem
file, we can configure lighttpd to
start listening on port 443
for the domain and serve HTTPS using the new
certificate. So I added a stanza like this to the lighttpd configuration:
$SERVER["socket"] == "<ip>:443" {
ssl.engine = "enable"
ssl.pemfile = "/etc/letsencrypt/live/<domain>/everything.pem"
ssl.ca-file = "/etc/letsencrypt/live/<domain>/chain.pem"
ssl.honor-cipher-order = "enable"
ssl.use-compression = "disable"
ssl.cipher-list = "ECDHE-RSA-AES128-GCM-SHA256:..."
ssl.use-sslv2 = "disable"
ssl.use-sslv3 = "disable"
}
And, after a restart of lighttpd, treibut.net
is back online the way
things should be served these days.