
Caddy Reverse Proxy with Automatic HTTPS for Multiple Apps
- AuthorLiam K.
- Date
- Time17 minutes
Caddy is a modern web server and reverse proxy that provisions and renews TLS certificates automatically. Compared to manual Certbot workflows, Caddy reduces operational overhead while keeping strong defaults for HTTPS redirects, HTTP/2, and optional HTTP/3. This guide walks through a production-ready multi-app setup on Ubuntu or Debian.
Prerequisites
- Ubuntu 22.04+ or Debian 12 server with a public IP
- DNS A/AAAA records pointing to the server for each hostname
- Backend apps listening on localhost (e.g. ports 3000 and 8080)
- Ports 80 and 443 open in your firewall
Step 1: Install Caddy
sudo apt update
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 -y caddy
caddy versionStep 2: Create the Caddyfile
Replace the example domains with your own. Caddy obtains certificates on first request and renews them automatically before expiry.
sudo tee /etc/caddy/Caddyfile >/dev/null <<'EOF'
{
email admin@example.com
admin off
}
(common_headers) {
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-Content-Type-Options nosniff
X-Frame-Options DENY
Referrer-Policy strict-origin-when-cross-origin
-Server
}
}
app.example.com {
import common_headers
encode gzip zstd
reverse_proxy 127.0.0.1:3000
}
api.example.com {
import common_headers
encode gzip zstd
reverse_proxy 127.0.0.1:8080 {
header_up Host {host}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
}
}
EOFStep 3: Validate and Reload
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl enable caddy
sudo systemctl reload caddy
sudo systemctl status caddy --no-pagerStep 4: Verify TLS and Routing
curl -I https://app.example.com
curl -I https://api.example.com/health
sudo journalctl -u caddy -n 50 --no-pagerStep 5: Enable HTTP/3 (Optional)
HTTP/3 uses QUIC over UDP port 443. Open UDP 443 in your firewall before enabling it.
sudo ufw allow 443/udp
# Add to the global options block in /etc/caddy/Caddyfile:
# {
# servers {
# protocols h1 h2 h3
# }
# }
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddyStep 6: Add Basic Rate Limiting
# Install rate limit module if not bundled, then add to a site block:
# rate_limit {
# zone api_limit {
# key {remote_host}
# events 100
# window 1m
# }
# }Production Checklist
- Keep backend services bound to 127.0.0.1 — only Caddy should be public-facing.
- Monitor certificate renewal events in journalctl -u caddy.
- Use separate hostnames per service to isolate blast radius and simplify routing.
- Test config changes with caddy validate before every reload.
- Back up /etc/caddy/Caddyfile and document ownership for on-call.
"Automatic HTTPS only saves time when your proxy config is version-controlled, validated, and owned by the same team that runs production."



