Skip to content

Instantly share code, notes, and snippets.

@ifeulner
Last active July 9, 2026 10:58
Show Gist options
  • Select an option

  • Save ifeulner/f558ab9c579dcea3e61323cacbe1bdbd to your computer and use it in GitHub Desktop.

Select an option

Save ifeulner/f558ab9c579dcea3e61323cacbe1bdbd to your computer and use it in GitHub Desktop.
Authentik forward_auth via Caddy across two hosts (embedded outpost): two silent-bypass gotchas and the fix

Authentik forward_auth via Caddy, across two separate hosts (embedded outpost)

Setup: Authentik's embedded outpost (no separate ghcr.io/goauthentik/proxy container) protecting an app on a different host than Authentik, via Caddy's forward_auth. Community examples assume Caddy and Authentik are on the same machine — the two-host case has two extra gotchas that silently bypass auth instead of erroring.

Symptom: config validates fine, Provider/Application exist and are bound to the embedded outpost — but every request reaches the protected app, no matter the auth state. No redirect, no error.

Root cause 1 — Caddy ≥2.11 auto-rewrites Host for HTTPS upstreams

Adding header_up Host {host} (to preserve the original host for Authentik) breaks Host B's own Caddy routing, which is keyed on auth.example.com. A request with Host: app.example.com matches nothing there, and Caddy's default for "nothing matched, no catch-all" is Go's http.ResponseWriter default: an empty 200 OK — not 404, not 502. forward_auth sees 2xx and waves the request through.

Fix: don't override Host. Caddy's default rewrite to the upstream host is what you want here.

Root cause 2 — Caddy discards incoming X-Forwarded-* by default

With Host fixed, requests reach Authentik but now always get a 404 "no app for hostname". Authentik picks the Provider via X-Forwarded-Host (preferred over Host — see internal/utils/web/host.go). Host A sets it correctly, but Host B's Caddy overwrites X-Forwarded-Host on its own hop to 127.0.0.1:9000 (Caddy ignores incoming X-Forwarded-* by default, to prevent spoofing).

Fix: trust Host A on Host B's Caddy so it stops overwriting the header.

Working config

Host A (app.example.com, protected app):

(authentik_forwardauth) {
	reverse_proxy /outpost.goauthentik.io/* https://auth.example.com
	forward_auth https://auth.example.com {
		uri /outpost.goauthentik.io/auth/caddy
		copy_headers X-Authentik-Username X-Authentik-Groups X-Authentik-Email X-Authentik-Name X-Authentik-Uid X-Authentik-Jwt X-Authentik-Meta-Jwks X-Authentik-Meta-Outpost X-Authentik-Meta-Provider X-Authentik-Meta-App X-Authentik-Meta-Version
		trusted_proxies private_ranges
	}
}

app.example.com {
	import authentik_forwardauth
	reverse_proxy 127.0.0.1:8080
}

No header_up Host override anywhere — that's the point.

Host B (auth.example.com, Authentik):

auth.example.com {
	reverse_proxy 127.0.0.1:9000 {
		trusted_proxies <HOST_A_PUBLIC_IP>/32
	}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment