Caddy Reverse Proxy Notes

Caddy Reverse Proxy Notes

This note records the Caddy setup I keep reusing for small VPS services.

Caddy is good for this use case because the config is short and ACME certificate management is automatic. Most app services can stay on 127.0.0.1, while Caddy is the only public HTTP/TLS entry.

Example domains and ports:

  • newapi.example.com:8445 -> 127.0.0.1:3000
  • ds2api.example.com:8446 -> 127.0.0.1:6011
  • pt.example.com:8443 -> 127.0.0.1:18080
  • bot.example.com:8444 -> 127.0.0.1:6185

Install On Debian

1
2
3
4
5
6
7
8
9
10
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

On Arch Linux:

1
sudo pacman -S caddy

Basic Caddyfile

Edit:

1
sudo vim /etc/caddy/Caddyfile

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
newapi.example.com:8445 {
reverse_proxy 127.0.0.1:3000
}

ds2api.example.com:8446 {
reverse_proxy 127.0.0.1:6011
}

pt.example.com:8443 {
reverse_proxy 127.0.0.1:18080
}

bot.example.com:8444 {
reverse_proxy 127.0.0.1:6185
}

Validate before reload:

1
2
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy

If reload is not enough:

1
2
sudo systemctl restart caddy
sudo journalctl -u caddy -e --no-pager

Ports And ACME

For normal automatic certificates, Caddy needs to complete ACME challenges. Keep these in mind:

  • 80 should be reachable for HTTP-01 challenges.
  • 443 should be reachable for normal HTTPS sites.
  • If another service owns 443, Caddy can still serve HTTPS on another port, but certificate issuance may need 80 or DNS challenge.

Example firewall rules:

1
tcp dport { 80, 443, 8443, 8444, 8445, 8446 } accept

Bind Apps To Localhost

The app should usually listen on localhost:

1
2
3
4
127.0.0.1:3000
127.0.0.1:6011
127.0.0.1:18080
127.0.0.1:6185

This prevents users from bypassing Caddy and hitting the raw app port directly.

Notes

  • Put one service block per tool. Do not mix service-specific config into a generic Caddy note.
  • Caddyfile changes should be validated before reload.
  • If the app rejects proxied requests because of host header validation, fix that in the app config deliberately instead of exposing the app port.
  • Do not publish real internal domains or private ports if they reveal your infrastructure layout.