Migration to wildcard letsencrypt certificate for all services

I have been running on a wildcard certificate for many years on my server. While there are concerns that these are less secure because they violate the rule of not keeping all eggs in one basket, the main reason for my migration was cost (and I prefer to stick to the wildcard for convenience).

Lets Encrypt started providing wildcard certificates about a year ago. The only issue is it requires DNS validation for authorization. I keep my domains at Gandi and while they have a DNS API, they stopped maintaining the module to favor their free certificates for whoever uses their hosting. Since I don’t, I had to tinker a little bit to get letsencrypt work for my wildcard *.domain.com and in this article I will explain how.

To be able to use the DNS API, an API key is required. It can be obtained from the Gandi account’s security section. I have comitted mine to /etc/gandi.ini, the content is as follows:

root@mydomain# cat /etc/gandi.ini
certbot_plugin_gandi:dns_api_key=whatever

This will allow the gandi-plugin for certbot to create a TXT record with some required value, which will then be verified by the CA and cleaned up automatically.

In the next step I am adding the unsupported plugin to the certbot docker image by creating the following Dockerfile

root@mydomain# cat Dockerfile

FROM certbot/certbot

RUN pip install --no-cache-dir certbot-plugin-gandi

Followed by building the new image:

root@mydomain# docker build -t letsencrypt-gandi .

root@mydomain# letsencrypt-gandi # docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
letsencrypt-gandi latest 9a4bc7f0212e 2 hours ago 158MB

In the next step, authenticate and generate the certificates. They will only be valid for a short time, so they need to be regularly renewed.

root@mydomain# cat letsencrypt.sh

#!/bin/zsh

docker run -it --rm \
--volume "/etc/letsencrypt:/etc/letsencrypt" \
--volume "/var/lib/letsencrypt:/var/lib/letsencrypt" \
--volume "/etc/gandi.ini:/etc/gandi.ini":ro \
--name=certbot \
letsencrypt-gandi certonly -a certbot-plugin-gandi:dns --certbot-plugin-gandi:dns-credentials /etc/gandi.ini --server https://acme-v02.api.letsencrypt.org/directory -d \*.domain.com

Note the mandatory escaping of the asterisk.

Finally, the docker script which needs to be added to cron to auto-renew the certificates:

docker run -it --rm \
--volume "/etc/letsencrypt:/etc/letsencrypt" \
--volume "/var/lib/letsencrypt:/var/lib/letsencrypt" \
--volume "/etc/gandi.ini:/etc/gandi.ini":ro \
--name=certbot \
letsencrypt-gandi renew -q -a certbot-plugin-gandi:dns --certbot-plugin-gandi:dns-credentials /etc/gandi.ini --server https://acme-v02.api.letsencrypt.org/directory

After those steps, changing paths to the new private keys and certificate bundles is required for all services. Here’s what I had to do:

  • Dovecot – nice and easy
  • Nextcloud – see nginx
  • exim – needs copying the key/cert pair to some other directory where it can access it as the user it runs with, chmod 400 is sufficient however be mindful you need +x to traverse directories and +r to list their contents.
  • nginx – massive replacement of the key/cert paths can be done by moving them to a separate file under /etc/nginx/conf.d/ and loaded as a module
  • rainloop – must have the “verify certificate” option disabled, not sure why, but it’s marked as “unstable” by the vendor anyway.

That’s it – hope it helps!