How I Configured Caddy as a Reverse Proxy for My Home Lab and Solved the HTTPS Redirect Bug

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.log for 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 caddy will reveal if the service crashed after a reload.
  • Redirect loops: If a service still redirects to :443, re‑inspect the header_upstream block 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