Caddy Reverse Proxy¶
Caddy is the simplest option for automatic HTTPS. It handles Let's Encrypt certificates with zero configuration and is a great choice for home servers and Raspberry Pi deployments.
Prerequisites¶
- Dreadnought running via Docker Compose (or systemd)
- A domain name with DNS pointing to your server
- Caddy installed
Installing Caddy¶
Ubuntu/Debian:
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy
Or run as a Docker container (see below).
Step 1 — Set NEXT_PUBLIC_API_URL¶
Edit docker-compose.yml:
web:
environment:
- NEXT_PUBLIC_API_URL=https://ddns-api.yourdomain.com
- NODE_ENV=production
Rebuild:
docker compose up -d --build
Step 2 — Configure the Caddyfile¶
Edit /etc/caddy/Caddyfile:
# Web UI
ddns.yourdomain.com {
reverse_proxy localhost:8082
}
# API
ddns-api.yourdomain.com {
reverse_proxy localhost:8081
}
That's it. Caddy automatically: - Obtains and renews Let's Encrypt certificates - Redirects HTTP to HTTPS - Sets secure TLS defaults
Step 3 — Reload Caddy¶
sudo systemctl reload caddy
# Check status
sudo systemctl status caddy
# Test configuration
caddy validate --config /etc/caddy/Caddyfile
Step 4 — Verify¶
curl -I https://ddns.yourdomain.com
curl https://ddns-api.yourdomain.com/health
Using Caddy on a Raspberry Pi¶
The same process works on a Pi. One extra step — open ports 80 and 443 if you're using UFW:
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
And set up port forwarding on your router: external 443 → Pi's IP:443, and external 80 → Pi's IP:80 (needed for certificate issuance via HTTP challenge).
Running Caddy as a Docker Container¶
If you prefer to run Caddy in Docker alongside Dreadnought:
Create /opt/caddy/Caddyfile:
ddns.yourdomain.com {
reverse_proxy dreadnought-ddns-web-1:3000
}
ddns-api.yourdomain.com {
reverse_proxy dreadnought-ddns-api-1:8000
}
Create /opt/caddy/docker-compose.yml:
services:
caddy:
image: caddy:2-alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp" # HTTP/3
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
networks:
- dreadnought
volumes:
caddy_data:
caddy_config:
networks:
dreadnought:
external: true
name: dreadnought-ddns_default # Matches Dreadnought's compose network
Start Caddy:
cd /opt/caddy
docker compose up -d
Find the Dreadnought network name:
docker network ls | grep dreadnought
Single Domain Setup (No Separate API Subdomain)¶
If you only have one domain and want both frontend and API under the same host:
ddns.yourdomain.com {
# API requests go to the backend
handle /api/* {
reverse_proxy localhost:8081
}
handle /health {
reverse_proxy localhost:8081
}
# Everything else goes to the frontend
handle {
reverse_proxy localhost:8082
}
}
Set NEXT_PUBLIC_API_URL=https://ddns.yourdomain.com and rebuild.
Cloudflare DNS Challenge (No Ports 80/443 Required)¶
If you can't open ports (e.g. ISP blocks them, or you're using a carrier-grade NAT), Caddy supports the Cloudflare DNS challenge for certificate issuance. This requires the caddy-dns/cloudflare module.
Install Caddy with the Cloudflare plugin:
# Using xcaddy to build a custom Caddy binary
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
xcaddy build --with github.com/caddy-dns/cloudflare
Or use the Docker image with the plugin pre-installed:
services:
caddy:
image: ghcr.io/slothcroissant/caddy-cloudflaredns:latest
# ... rest of config
Caddyfile with DNS challenge:
{
acme_dns cloudflare your-cloudflare-api-token
}
ddns.yourdomain.com {
reverse_proxy localhost:8082
}
ddns-api.yourdomain.com {
reverse_proxy localhost:8081
}
Adding Security Headers¶
ddns.yourdomain.com {
reverse_proxy localhost:8082
header {
X-Frame-Options DENY
X-Content-Type-Options nosniff
Referrer-Policy strict-origin-when-cross-origin
-Server
}
}
Troubleshooting Caddy¶
Certificate not issued¶
# Check logs
sudo journalctl -u caddy -f
# Common causes:
# - Port 80 not reachable from the internet (needed for HTTP challenge)
# - DNS not pointing to your server yet
# - Rate limit hit (too many cert requests for same domain)
502 Bad Gateway¶
Caddy can reach the site but the backend isn't responding:
docker compose ps # Verify containers are running
curl http://localhost:8082 # Test frontend directly
curl http://localhost:8081/health # Test API directly
Caddy config syntax error¶
caddy validate --config /etc/caddy/Caddyfile