Quick and easy Valid SSL for your homelab with Caddy and ACME DNS-01

Secure all of your self-hosted services and (if you choose to) never expose them publicly with a DNS validated SSL certificate.

If you’re self-hosting local web apps in your homelab, chances are, you’re accessing them with a combination of an IP address and a port e.g. http://192.168.1.113:80.

However, at some point, you might get tired of memorizing IP addresses, so you’ll switch to a combination of a reverse proxy and local domain names, such as https://vaultwarden.local or https://homeassistant.local.

After doing that, you’ll quickly notice that you get an annoying browser warning every time you visit your applications. Yay!

Of course there are some workarounds:

  1. Create a local Certificate Authority, generate your own certificates and trust them on every device you use. 😫

  2. Add an execption in every browser for every device you use. 🫤

  3. Ignore it, everytime 😐

But there is another way, you can serve your local web apps with a valid SSL and nice easy to remember URL. This is where DNS-01 steps in.

But how does it work?

Let me break down how Let’s Encrypt handles certificate validation and creation in simpler terms.

Usually, to get a Let’s Encrypt certificate, we’d have certbot set up a quick web server on the machine where we want the certificate. Then, Let’s Encrypt checks for a secret code on that server. If it finds it, voila! We get our certificate using what’s called HTTP-01.

But here’s the catch: with HTTP-01, the machine needs to be reachable by Let’s Encrypt’s servers and the domain must point to that machine’s IP address.

Enter DNS-01! It’s like a magic workaround. Instead of directly checking the machine, it uses a special DNS record to verify ownership. Bonus: DNS-01 allows wildcard certificates for our domains. So, if we have a bunch of subdomains like vaultwarden.homelab.me or homeassistant.homelab.me we can snag a certificate for *.homelab.me, covering all those subdomains in one go. Pretty cool, right?

What you will need

  1. A domain name with a registrar that supports Let’s Encrypt DNS-01 validation. I like Porkbun for this.

  2. A Reverse Proxy, I’ll be using Caddy for this.

  3. An OS, I’m using Ubuntu Server 20.03. But Caddy works on many OS’s and Achitectures.

Ok, let’s get started

Caddy provides automatic HTTPS TLS certificates for all your sites and keeps them renewed. To get DNS validation working we need a special build which can connect to the Porkbun API go here to find a binary that supports your registrar and download it.

You can find the official manual instructions here, or follow from below.

Move the caddy binary into your $PATH

sudo mv caddy /usr/bin/

Test it worked

caddy version

Create a new group

sudo groupadd --system caddy

Create a user named caddy with a writeable home directory

sudo useradd --system \
    --gid caddy \
    --create-home \
    --home-dir /var/lib/caddy \
    --shell /usr/sbin/nologin \
    --comment "Caddy web server" \
    caddy

Create your service file for systemctl

sudo touch /etc/systemd/system/caddy.service

Populate it as below

[Unit]
Description=Caddy
Documentation=https://caddyserver.com/docs/
After=network.target network-online.target
Requires=network-online.target

[Service]
Type=notify
User=caddy
Group=caddy
ExecStart=/usr/bin/caddy run --environ --config /etc/caddy/Caddyfile
ExecReload=/usr/bin/caddy reload --config /etc/caddy/Caddyfile --force
TimeoutStopSec=5s
LimitNOFILE=1048576
LimitNPROC=512
PrivateTmp=true
ProtectSystem=full
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE

[Install]
WantedBy=multi-user.target

Create your Caddyfile to store your config

sudo mkdir /etc/caddy
sudo touch /etc/caddy/Caddyfile
sudo chown caddy /etc/caddy/Caddyfile

Start the service for the first time 🤞

sudo systemctl daemon-reload
sudo systemctl enable --now caddy

Check it’s running

systemctl status caddy

Setting up the config file

This will depend on the services on your network you want to secure, for this example I will use vaultwarden and audiobookshelf as examples.

If you are using Porkbun, go ahead and grab your API key from https://porkbun.com/account/api.

Open it with your editor of choice:

sudo vim /etc/caddy/Caddyfile
*.my-domain-name.com {
tls <email-address-for-cert>
tls {
        dns porkbun {
                api_key <key-goes-here>
                api_secret_key <key-goes-here>
        }
}

# vaultwarden
@vault host vault.my-domain-name.com

handle @vault {
        reverse_proxy server_ip:server_port
}

# audiobookshelf
@bookshelf host bookshelf.my-domain-name.com

handle @bookshelf {
        encode gzip zstd
        reverse_proxy 192.168.1.2:12345
}

log {
        output file /var/log/caddy/access.log {
                roll_size 10mb
                roll_keep 5
                roll_keep_for 24h
        }
}
}

Save the file and reload Caddy

sudo systemctl reload caddy

This is the best bit, the above config file is all you need. Try and visit one your apps, e.g vault.my-domain-name.com.

You are done! 🚀

Dec 7, 2023