If you run more than one web app on a single VPS — a site on port 3000, an API on 8080, a dashboard on 9000 — you don't want visitors typing port numbers. You want app.example.com and api.example.com to just work, over HTTPS, without you hand-rolling TLS certificates. That's what a reverse proxy does, and Caddy makes it about as painless as it gets.
Why Caddy
Nginx and Traefik are both excellent. Caddy earns its place because of one feature: it provisions and renews TLS certificates from Let's Encrypt automatically, with zero config beyond naming your domain. No certbot cron job, no renewal surprises at 2 a.m. For a small operator that one thing removes a whole category of outages.
Prerequisites
- A VPS with root (any KVM box with full root will do — distro doesn't much matter).
- A domain with DNS pointing at your server's public IP. If your VPS uses NAT IPv4, make sure the HTTP/HTTPS ports you need are actually forwarded to it — check with your host before you debug for an hour.
- Ports 80 and 443 open in your firewall.
Install
On Debian or Ubuntu:
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
Caddy installs as a systemd service and starts immediately.
The Caddyfile
The entire config for proxying two apps lives in /etc/caddy/Caddyfile:
app.example.com {
reverse_proxy localhost:3000
}
api.example.com {
reverse_proxy localhost:8080
}
Reload it:
sudo systemctl reload caddy
That's the whole thing. The first time a request hits app.example.com, Caddy fetches a certificate, serves it over HTTPS, and redirects plain HTTP to HTTPS for you. No certbot, no renewal timer.
Useful extras
Gzip/zstd compression and a security header, added inline:
app.example.com {
encode zstd gzip
header Strict-Transport-Security "max-age=31536000;"
reverse_proxy localhost:3000
}
Serve static files and proxy an API under one host:
example.com {
root * /var/www/site
file_server
handle /api/* {
reverse_proxy localhost:8080
}
}
A couple of gotchas
- DNS first. Caddy can't get a certificate for a name that doesn't resolve to your box yet. Let DNS propagate before you reload.
- Rate limits. Let's Encrypt limits how many certs you can request per week. While testing, point Caddy at the staging CA so you don't burn your quota.
-
Logs.
journalctl -u caddy -fshows you exactly what it's doing during the certificate dance — read it the first time, it's reassuring.
Closing note
Caddy turns "I have three things running on one box" into three clean HTTPS hostnames in about ten minutes. Any full-root KVM VPS handles it comfortably — we run a small one for exactly this at overnight.host, in whichever region you pick, with NAT-IPv4 port forwarding disclosed up front so the DNS step doesn't surprise you.
Top comments (0)