DEV Community

overnight.host
overnight.host

Posted on

Reverse proxy on a VPS with Caddy — automatic HTTPS in minutes

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
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

Reload it:

sudo systemctl reload caddy
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

Serve static files and proxy an API under one host:

example.com {
    root * /var/www/site
    file_server
    handle /api/* {
        reverse_proxy localhost:8080
    }
}
Enter fullscreen mode Exit fullscreen mode

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 -f shows 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)