Wildcard SSL certificate

Linux

Self-generated certificate is good for bootstrapping purpose but sooner than later we need a valid SSL certificate for all services (IMAPS, HTTPS, DoT etc).

1. Install

  pacman -S certbot certbot-nginx

2.1 Manual generation

Init the certificate generation

  certbot certonly --manual -d "*.costan.ro" -d "costan.ro"

The above tool spits out a DNS challenge that we need to set as a DNS TXT record.

Set the DNS TXT record

It depends on your DNS provider but usually is as simple as adding a new record.

  grep acme /etc/nsd/zones/costan.ro
_acme-challenge                       IN  TXT     "PDFYVU1v3Jlo6Yeo50-LHiokC8PwNeZl_-FCz13CKPs"

Check using local DNS server first (mind @ns.costan.ro local DNS server at the end)

  dig +short TXT _acme-challenge.costan.ro @ns.costan.ro
PDFYVU1v3Jlo6Yeo50-LHiokC8PwNeZl_-FCz13CKPs

Wait for DNS propagation and check using public (or default) DNS server. This "wait" part is important otherwise LetsEncrypt won't be able to resolve the DNS challenge and SSL generation process will fail.

  dig +short TXT _acme-challenge.costan.ro @8.8.8.8
PDFYVU1v3Jlo6Yeo50-LHiokC8PwNeZl_-FCz13CKPs

Finish the certificate generation

Once the DNS TXT change is propagated press "ENTER" and certbot tool will generate all certificate files.

List the certificate

  certbot certificates
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Found the following certs:
  Certificate Name: costan.ro
    Serial Number: 4f6208230dcfb39f339295c19a5eb7781fa
    Key Type: ECDSA
    Domains: *.costan.ro
    Expiry Date: 2023-03-19 12:40:17+00:00 (VALID: 89 days)
    Certificate Path: /etc/letsencrypt/live/costan.ro/fullchain.pem
    Private Key Path: /etc/letsencrypt/live/costan.ro/privkey.pem
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Usage

Change configuration files for all public services (Postfix, Dovecot, Nsd, etc) and point to newly generated fullchain.pem and privkey.pem files.

  postconf -n | grep -E "smtpd_tls_.*_file"
smtpd_tls_cert_file = /etc/letsencrypt/live/costan.ro/fullchain.pem
smtpd_tls_key_file = /etc/letsencrypt/live/costan.ro/privkey.pem
  doveconf ssl_cert ssl_key
ssl_cert = </etc/letsencrypt/live/costan.ro/fullchain.pem
ssl_key = </etc/letsencrypt/live/costan.ro/privkey.pem

Checker

And last, check installed SSL cert using an SSLlabs online checker or openssl tool.

  openssl s_client -connect www.costan.ro:443

2.2 Semi-automated renewal

Config file

Basic config files with a few cmd line settings.

  cat /etc/letsencrypt/cli.ini
key-type = ecdsa
email = letsencrypt@costan.ro
authenticator = manual
agree-tos = true

Auth script

This script will update _acme-challenge in DNS config, sign and reload the zone.

  cat /etc/letsencrypt/renewal-hooks/auth.sh
#!/usr/bin/env sh

echo CERTBOT_DOMAIN=$CERTBOT_DOMAIN
echo CERTBOT_VALIDATION=$CERTBOT_VALIDATION
echo CERTBOT_REMAINING_CHALLENGES=$CERTBOT_REMAINING_CHALLENGES
echo CERTBOT_ALL_DOMAINS=$CERTBOT_ALL_DOMAINS

echo Sed: replace _acme_challenge in /etc/nsd/zones/$CERTBOT_DOMAIN ...:
sed -Ei "s/_acme-challenge(.+)\"(.+)\"/_acme-challenge\1\"$CERTBOT_VALIDATION\"/" /etc/nsd/zones/$CERTBOT_DOMAIN

echo NSD: sign/reload $CERTBOT_DOMAIN zone...
cd /etc/nsd/zones/
ldns-signzone -n -p -e $(date -d "$(date) +3 month" +%Y%m%d%H%M%S) costan.ro Kcostan.ro.+008+03304 Kcostan.ro.+008+19957
nsd-control reconfig
nsd-control reload $CERTBOT_DOMAIN

echo Wait for DNS propagation...
sleep 15s

echo "DONE."

Deploy script

This script restarts all services that uses widlcard certificate.

  cat /etc/letsencrypt/renewal-hooks/deploy/deploy.sh
#!/usr/bin/env sh

echo Restarting Nginx...
systemctl restart nginx

echo Restarting Postfix...
systemctl restart postfix

echo Restarting Dovecot...
systemctl restart dovecot

echo DEPLOY-HOOK: DONE.

Renew

And finally the magic command.

  certbot renew --manual-auth=/etc/letsencrypt/renewal-hooks/auth.sh -v
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/costan.ro.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Certificate is due for renewal, auto-renewing...
Plugins selected: Authenticator manual, Installer None
Renewing an existing certificate for *.costan.ro
Performing the following challenges:
dns-01 challenge for costan.ro
Running manual-auth-hook command: /etc/letsencrypt/renewal-hooks/auth.sh
Hook '--manual-auth-hook' for costan.ro ran with output:
CERTBOT_DOMAIN=costan.ro
CERTBOT_VALIDATION=uY5-sXNpcCtIQoVmA7jy-kIg4-ipseBLjjOzyoTQzpI
CERTBOT_REMAINING_CHALLENGES=0
CERTBOT_ALL_DOMAINS=costan.ro
Sed: replace _acme_challenge in /etc/nsd/zones/costan.ro ...:
NSD: sign/reload costan.ro zone...
reconfig start, read /etc/nsd/nsd.conf
ok
ok
Wait for DNS propagation...
DONE.
Waiting for verification...
Cleaning up challenges

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Congratulations, all renewals succeeded:
/etc/letsencrypt/live/costan.ro/fullchain.pem (success)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Testing

  certbot certificates
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Found the following certs:
  Certificate Name: costan.ro
    Serial Number: 3fc9af33d62a240238dc4c1fb3a3dc616e6
    Key Type: ECDSA
    Domains: *.costan.ro
    Expiry Date: 2023-06-14 10:13:39+00:00 (VALID: 89 days)
    Certificate Path: /etc/letsencrypt/live/costan.ro/fullchain.pem
    Private Key Path: /etc/letsencrypt/live/costan.ro/privkey.pem
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

2.3 Fully-automated renewal

Start systemd service that runs twice a day and checks/executes any due renewal.

  systemctl start certbot-renew.timer
  systemctl enable certbot-renew.timer

After automatic generation ran we end up with brand new certificate.

  certbot certificates
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Found the following certs:
  Certificate Name: costan.ro
    Serial Number: 3f385ce1c48f559cac06c8ba0f7bd13521c
    Key Type: ECDSA
    Domains: *.costan.ro
    Expiry Date: 2023-09-12 02:57:29+00:00 (VALID: 89 days)
    Certificate Path: /etc/letsencrypt/live/costan.ro/fullchain.pem
    Private Key Path: /etc/letsencrypt/live/costan.ro/privkey.pem
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

And now we have 3 certificates in Let's Encrypt archive, one at each 3 months.

  ls -l /etc/letsencrypt/archive/costan.ro/
total 60
-rw-r--r-- 1 root root 1558 Dec 19 15:40 cert1.pem
-rw-r--r-- 1 root root 1558 Mar 16 13:13 cert2.pem
-rw-r--r-- 1 root root 1558 Jun 14 06:57 cert3.pem
-rw-r--r-- 1 root root 3749 Dec 19 15:40 chain1.pem
-rw-r--r-- 1 root root 3749 Mar 16 13:13 chain2.pem
-rw-r--r-- 1 root root 3749 Jun 14 06:57 chain3.pem
-rw-r--r-- 1 root root 5307 Dec 19 15:40 fullchain1.pem
-rw-r--r-- 1 root root 5307 Mar 16 13:13 fullchain2.pem
-rw-r--r-- 1 root root 5307 Jun 14 06:57 fullchain3.pem
-rw------- 1 root root  241 Dec 19 15:40 privkey1.pem
-rw------- 1 root root  241 Mar 16 13:13 privkey2.pem
-rw------- 1 root root  241 Jun 14 06:57 privkey3.pem

Updates

  • [2022-12-19] - initial wildcard certificate
  • [2023-03-16] - manual renew of wildcard certificate
  • [2023-06-14] - automatic renew of wildcard certificate
  • [2024-04-16] - add deploy.sh script