Origin

Every self-hosted operator eventually ends up with a Caddyfile that does the same handful of things: several subdomains, some gated areas behind an auth backend, a couple of dynamic apps reverse-proxied with WebSocket support, and a friendly login redirect rather than a bare 401.

caddy-multisite is that Caddyfile, with the deployment-specific identifiers stripped and the structure that actually does the work preserved. It is the assembled production shape, not a single-directive demonstration.

What is in it

Multi-subdomain static hosting

One Caddyfile, several site roots under /srv/*, each driven by {$SITE_DOMAIN} environment substitution. Reusable snippets — (static_site) and (security_headers) — collapse the per-site block to two lines.

forward_auth gating

Any path can be put behind a single /verify endpoint on your auth service. Two-hundred is allow, four-oh-one is deny, and an X-Required-Tier hint is forwarded so the backend can scope its check. Identity headers come back via copy_headers.

401 to login redirect

A handle_errors block catches the backend's 401 and turns it into /login/?next=<original-url>. The user lands on a sign-in page that knows where to send them after authentication completes.

Reverse proxy with WebSockets

Generic block for fronting a dynamic backend. Connection upgrades are handled by Caddy automatically; only X-Real-IP is set explicitly — the rest are forwarded by default.

The auth pattern

The whole gated-area shape distilled into one block. Your backend supplies a single GET /verify endpoint; the redirect, header forwarding, and error handling are Caddy's responsibility.

handle /private* { forward_auth {$AUTH_BACKEND} { uri /verify copy_headers X-User-Id X-User-Email X-User-Tier header_up X-Required-Tier "member" } file_server } handle_errors { @unauth expression `{err.status_code} == 401` redir @unauth /login/?next={http.request.uri} 302 }

Why it is public

Caddy's documentation covers each of these directives individually. What is missing is the assembled production shape — and the production shape is what spares you re-deriving it on every box you set up.

The template ships with placeholder static sites so it boots out of the box, and a documented /verify contract you can satisfy with Authelia, tinyauth, oauth2-proxy, or a thirty-line service of your own.

caddy-multisite on GitHub

Caddyfile, compose, the documented auth contract, and an example layout.