Setting up Caddy as a reverse proxy for my home lab
I’ve been juggling a Plex server, Nextcloud, and a little Go API all on the same Ubuntu 24.04 box. The goal was simple: a tidy public URL for each service, automatic HTTPS, and as little upkeep as possible. Caddy 2.7 fit the bill perfectly—its ACME integration is a one‑liner, and it ships with HTTP/2 out of the box.
1. Install Caddy
sudo apt update
sudo apt install -y debian-keyring debian-archive-keyring
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | \
sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/caddy-stable-archive-keyring.gpg] \
https://dl.cloudsmith.io/public/caddy/stable/deb/debian any main" | \
sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install -y caddy
Caddy boots as a systemd service automatically. Just double‑check that it’s listening on the web ports:
sudo ss -tulpn | grep caddy
2. Basic Caddyfile
Create /etc/caddy/Caddyfile:
{
email [email protected]
acme_ca https://acme-v02.api.letsencrypt.org/directory
}
plex.example.com {
reverse_proxy 192.168.1.10:32400
}
nextcloud.example.com {
reverse_proxy 192.168.1.11:80
}
api.example.com {
reverse_proxy 192.168.1.12:8080
}
The global block tells Caddy to use Let’s Encrypt and sets the admin email. Each site block maps a public domain to a local IP/port. Caddy will automatically fetch certs and redirect HTTP → HTTPS.
3. The HTTPS redirect bug
When I first spun Caddy up, hitting http://plex.example.com sent me to https://plex.example.com:443. The browser already defaults to 443, so that extra :443 looked weird. The culprit? The reverse_proxy directive didn’t forward the original scheme, so Plex kept appending the port in its own redirects.
Fix
Add a header_upstream block to each site:
plex.example.com {
reverse_proxy 192.168.1.10:32400 {
header_upstream X-Forwarded-Proto {http.request.scheme}
}
}
Do the same for Nextcloud and the API. The placeholder {http.request.scheme} tells Caddy to forward the incoming scheme (http or https) to the backend. Once Plex sees it was served over HTTPS, it stops tacking on :443.
4. Security tweaks
-
Drop HTTP/1.1: Plex and Nextcloud don’t need it, and turning it off shrinks the attack surface.
plex.example.com { reverse_proxy 192.168.1.10:32400 { header_upstream X-Forwarded-Proto {http.request.scheme} } http2 } -
HSTS: A global header keeps browsers on HTTPS.
{ header { Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" } } -
Rate limiting: Guard the API against brute‑force attacks.
api.example.com { reverse_proxy 192.168.1.12:8080 @login path /login limit @login 20r/m }
5. Reload and test
sudo systemctl reload caddy
Open each URL. The https:// bar should be clean, and the services should load without any port numbers showing up. A quick curl -I https://plex.example.com will confirm that the Location header doesn’t contain :443.
6. Troubleshooting
- Certificates not issued: Look in
/var/log/caddy/access.logfor ACME errors. Make sure your DNS A records point at the server’s public IP and that ports 80/443 are reachable from the internet. - 502 errors from the backend: Double‑check the internal IPs and ports.
systemctl status caddywill reveal if the service crashed after a reload. - Redirect loops: If a service still redirects to
:443, re‑inspect theheader_upstreamblock and ensure the backend isn’t forcing HTTPS on its own.
7. Maintenance
Caddy renews certs automatically. If you need to change the admin email or add a new domain, just edit the Caddyfile and reload. Keep the package fresh with sudo apt upgrade caddy.
That small tweak with header_upstream fixed the annoying redirect bug, and the rest of the setup was a breeze. Now my home lab services are reachable via clean HTTPS URLs, and I can spend more time on the apps themselves instead of chasing certificate plumbing.
See also
- How I Prevented a Rootless Podman Container From Filling My Disk With Logs
- Recovering a single config file from a corrupted Borg backup without restoring the whole archive
- Running My Home NAS Backups with `nice` and `ionice` So My Desktop Never Freezes
- Using a systemd path unit to clean my home server’s cache when disk space hits 90%
- How I Stopped /var/log from Flooding the Root Filesystem with Systemd Journal Rotations