Kuuko / Build it yourself

Build it yourself · Part 1Checked: 30 October 2026

Chapter 5: Certificates

Why

Your iPhone and Mac only talk to an encrypted resolver whose certificate they trust. A certificate from Let's Encrypt proves that dns.<your-domain> really is your server, and it's free and renews itself.

What you need

  • The domain at deSEC (chapter 2), the server with port 80 open in its firewall (chapter 3), and dnsdist running (chapter 4).
  • About 20 minutes (our estimate).

Steps

1. Give your resolver a name (at deSEC)

In deSEC, open your domain and add these records:

SubnameTypeValue
dnsAyour server's IPv4 address
dnsAAAAyour server's IPv6 address
(empty)CAA0 issue "letsencrypt.org"

The CAA record says that only Let's Encrypt may issue certificates for your domain. Any other certificate authority must refuse.

Check from your Mac, after a few minutes:

dig +short dns.<your-domain> A
dig +short dns.<your-domain> AAAA

Both show your server's addresses.

2. Install certbot (on the server)

sudo apt install certbot

3. A hook that hands each new certificate to dnsdist

Certbot keeps certificates in its own folder. This small script copies a new one to where dnsdist reads it, and restarts dnsdist. Certbot runs it after every successful renewal.

sudo nano /usr/local/sbin/dnsdist-cert-deploy

Paste:

#!/bin/sh
set -e
install -m 0644 -o root -g _dnsdist "$RENEWED_LINEAGE/fullchain.pem" /etc/dnsdist/tls/fullchain.pem
install -m 0640 -o root -g _dnsdist "$RENEWED_LINEAGE/privkey.pem"   /etc/dnsdist/tls/privkey.pem
systemctl restart dnsdist
  • $RENEWED_LINEAGE is the folder certbot fills in with the new certificate.
  • The certificate may be read by everyone; the private key only by root and dnsdist.

Make it executable:

sudo chmod 0755 /usr/local/sbin/dnsdist-cert-deploy

4. Get the certificate

sudo certbot certonly --standalone --preferred-challenges http \
  --non-interactive --agree-tos --register-unsafely-without-email \
  --key-type ecdsa --cert-name dns.<your-domain> -d dns.<your-domain> \
  --deploy-hook /usr/local/sbin/dnsdist-cert-deploy

What it does:

  • --standalone --preferred-challenges http: certbot briefly answers on port 80 itself, and Let's Encrypt checks that it reaches your server under that name.
  • --register-unsafely-without-email: no email address. You'll get no reminders, which is fine because renewal runs by itself.
  • --key-type ecdsa: a short, modern key.
  • --deploy-hook: run the script from step 3, now and after every renewal.

We used this "port 80" check from 23 to 26 October 2026, until we added a second server. The command above is rebuilt from our notes. If it fails, certbot's message says why; most often port 80 is closed in the firewall.

How you know it worked

  1. dnsdist has the real certificate:

    sudo openssl x509 -in /etc/dnsdist/tls/fullchain.pem -noout -subject -issuer -enddate

    shows CN = dns.<your-domain>, an issuer from Let's Encrypt, and an end date about 90 days ahead. From 10 February 2027, Let's Encrypt's certificates last 64 days by default, and shorter lifetimes are planned after that. Renewal keeps up by itself.

  2. The certificate is trusted, checked like your devices will:

    kdig @127.0.0.1 -p 443 +https=/dns-query/<your-private-path> +tls-ca +tls-hostname=dns.<your-domain> example.com A

    shows a TLS 1.3 session and status: NOERROR.

  3. From your Mac, over the internet (install kdig with brew install knot):

    kdig @dns.<your-domain> +https=/dns-query/<your-private-path> +tls-ca example.com A

    shows the same.

  4. Renewal will work:

    sudo certbot renew --dry-run
    systemctl list-timers certbot.timer

    The dry run succeeds, and the timer shows its next run.

Pitfalls

  • Port 80 must stay open for renewals. Closing it breaks renewal about two months later, quietly.
  • Two servers behind one name don't work with the port 80 check. Let's Encrypt reaches either server, so the check fails about half the time. If you add a second server later, use the DNS check instead: certbot writes a short-lived record at deSEC through its interface, with a token that may write only that one record. That's in "Going further".

Time and cost

  • About 20 minutes (our estimate).
  • Free.

Previous: Chapter 4: The resolver · Next: Chapter 6: Block lists that update themselves

Text: CC BY-SA 4.0 · Commands and configs: CC0