It took me quite a while to figure out how to get Let’s encrypt SSL certificates for my subdomains that are not accessible from the internet. I struggled to find the right resources so I thought it might be a good idea to document my findings here 😄
According to the caddy docs , the DNS challenge is the way to go if you want SSL certs for a servie thats not accessible from the internet. In order for it to work you need a caddy plugin for the DNS provider in use. Thankfully there is one for Hetzner already, which is my DNS provider.
Here’s my folder structure:
├── caddy
│ ├── Caddyfile
│ ├── config
│ ├── data
│ └── Dockerfile
└── docker-compose.yaml
Dockerfile 🔗
The first hurdle was that you cannot simply add a plugin to caddy, you have to build a custom caddy version. This is not very hard though, the Dockerfile for the custom build is quite simple:
1FROM caddy:builder AS builder
2RUN xcaddy build --with github.com/caddy-dns/hetzner@v0.0.1
3
4FROM caddy
5COPY --from=builder /usr/bin/caddy /usr/bin/caddy
Docker Compose 🔗
The docker-compose.yaml is also not very complex:
1version: "3.7"
2
3services:
4 caddy:
5 build: ./caddy
6 container_name: caddy
7 restart: unless-stopped
8 ports:
9 - "80:80"
10 - "443:443"
11 - "443:443/udp"
12 environment:
13 - HETZNER_API_KEY=7zyeTRpkcRtaxDC8G8oaYXSwSr4TjSSj6SU7SR7w
14 volumes:
15 - ./caddy/Caddyfile:/etc/caddy/Caddyfile
16 - ./caddy/data:/data
17 - ./caddy/config:/config
The important parts are build: ./caddy
and the env var HETZNER_API_KEY=7zyeTRpkcRtaxDC8G8oaYXSwSr4TjSSj6SU7SR7w
.
To get an API key, simply use the Hetzner web interface to generate one
Caddyfile 🔗
The caddyfile defines that it should use the DNS challenge with the Hetzner plugin:
1{
2 admin off
3 log
4}
5
6internal.bouni.de {
7 respond "Hello"
8 tls {
9 dns hetzner {env.HETZNER_API_KEY}
10 }
11}
The subdomain internal.bouni.de
is not record in Hetzners DNS server, only in my local DNS records (I use
pihole
for that).
Normally that would result in caddy not being able to get a cert for that subdomain.
But using the DNS challenge, caddy uses the Hetzner API to create a TXT record that holds a “certain value” (The actual words from the docs 😏) which allows Lets encrypt to verify that I’m in control of this domain. It then issues a cert which is used by caddy.