The self-hosted apps guide on this site already flagged Caddy as worth it “for how little configuration it needs compared to nginx for the same result.” Here’s exactly how little.

The whole config, for one app

This is a complete, working Caddyfile — not a trimmed-down example, the actual entire file:

yourdomain.com {
	reverse_proxy localhost:3000
}

That’s it. Point a domain’s DNS at your server, run Caddy with this file, and you get your app served over HTTPS with a real, auto-renewing certificate — Caddy provisions it from Let’s Encrypt automatically the first time it sees a request for that domain. No separate certbot setup, no cron job to renew certificates, no nginx config with a server block for port 80 that redirects to another server block for port 443.

Multiple apps, still one file

This is where a reverse proxy stops being a nice-to-have. Once you’re running more than one self-hosted service, a reverse proxy is what lets them all live behind port 443 on a single server, routed by domain:

app-one.yourdomain.com {
	reverse_proxy localhost:3000
}

app-two.yourdomain.com {
	reverse_proxy localhost:8080
}

home.yourdomain.com {
	reverse_proxy localhost:8096
}

Each block is independent. Add a new self-hosted app, add four lines, reload Caddy — no juggling port numbers in your head when you’re trying to remember which service was on which port.

Verified with Docker

The Docker Compose pattern is just as short, and this exact setup was tested end to end — Caddy reverse-proxying to a backend container by service name, real HTTP response coming back through Caddy on the frontend port:

services:
  caddy:
    image: caddy:2
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile

  your-app:
    image: your-app-image
yourdomain.com {
	reverse_proxy your-app:80
}

Docker Compose’s built-in service-name DNS resolution means your-app in the Caddyfile just works — no need to look up container IPs.

One honest caveat

The reverse-proxy mechanics above are tested and confirmed working. The automatic HTTPS certificate issuance specifically requires a real public domain with DNS pointed at a publicly reachable server on ports 80/443 — that part wasn’t (and can’t be) verified from a local sandbox, though it’s Caddy’s most well-documented and widely-relied-on feature.

If you don’t have a server to point a domain at yet, that’s the one prerequisite this whole setup depends on — a small DigitalOcean droplet is enough for everything described here, covered in more detail in the old laptop to home server post if you’d rather use hardware you already own instead.