Following Part 3 of the series, the final component to be set up is the Caddy reverse proxy. I previously used traefik and while it worked, there were a few hiccups I encountered. I can’t recall the exact issues I ran into, but they were enough for me to switch things up this time. On this particular VPS, I won’t be running many services so auto-discovery is irrelevant. I’m binding each service to a specific IP and port, then using Caddy to reverse proxy to them, which simplifies a lot of the management and admin. Caddy also handles automatic certificate renewal with my custom CA using the ACME endpoint I set up earlier, so that’s an added bonus.

For this specific use case, I am using Caddy to accept all incoming requests meant for AdGuard DNS and Smallstep CA and proxying them to the correct services. Each of these services will have their own private certificate that is generated by Caddy from our local Certificate Authority.

The Docker Compose file and configuration of Caddy is located in /containers/caddy base folder.

/containers/caddy/docker-compose.yaml

services:
  caddy:
    image: caddy:latest
    container_name: caddy
    restart: unless-stopped
    volumes:
      - /containers/smallstep/data/certs/root_ca.crt:etc/ssl/certs/root_ca.crt:ro # load our root CA into caddy
      - /containers/caddy/config:/etc/caddy
      - /containers/caddy/data:/data
    ports:
      - '192.168.200.1:80:80/tcp' # listen for HTTP on wireguard interface (tcp)
      - '192.168.200.1:443:443/tcp' # listen for HTTPS on wireguard interface (tcp)
    networks:
        - webproxy

networks:
    webproxy:
        external: true

Configure Caddy

Create the file /containers/caddy/config/Caddyfile and add the following information to it

{
  acme_ca https://smallstep-vps:9000/acme/handletec-vps/directory
  acme_ca_root /etc/ssl/certs/root_ca.crt
  email info@handletec.my
}

import /etc/caddy/config.d/*.conf

This config specifies we want to use our custom CA to generate certificates and with all our configuration in place, the Smallstep service will be able to connect back to Caddy for verification. We also allow including individual files for our services to more tidily manage them without cluttering the Caddyfile. We use the container name smallstep-vps to connect to the Smallstep service.

We create another file /containers/caddy/config/config.d/service.conf with the following contents

ca.vps.homelab.my {
  reverse_proxy https://smallstep-handletec-vps:9000
}

dns.vps.homelab.my {
  reverse_proxy http://adguardhome:8000
}

This is all that’s needed for our Caddy to automatically generate certificates per domain name. Notice how we use the container names as Docker can resolve these names using its internal DNS. The reverse_proxy directive is responsible for forwarding requests from each domain to the correct service.

Firewall

The final step is to allow the Docker network to connect to our Caddy and perform the validation of the domain name targets. Since our Caddy is listening on our specific Wireguard interface, we can create a rather lax rule to prevent any issues.

ufw allow proto tcp from any to any port 80

And that concludes our series on setting up a personal Certificate Authority for our own use. While I use this primarily for my own homelab, I can envision this being used in companies and organizations that want to utilize end-to-end TLS security for their own needs.

Happy reading!