Should You Centralize Login Across Twenty Sites?
Most agencies running more than a handful of WordPress installs eventually ask this question, then answer it wrong in both directions. Either they never centralize, and every client site has its own forgotten admin password rotting in a spreadsheet, or they centralize without thinking through what happens when the thing they centralized goes down. Both are failures of the same underlying mistake: treating identity as a checkbox instead of an architecture decision.
The Assumption Everyone Makes
Authentik publishes a clean forward-auth snippet for NGINX. Create a provider, assign an outpost, drop the config block in, reload. Fifteen minutes, according to the docs. That estimate assumes NGINX and Authentik share a Docker network and nothing else is in the request path.
Nothing in a real agency’s infrastructure looks like that. Separate servers, a CDN in front of everything, a tunnel between the identity server and the sites it’s protecting, twenty different domains with their own DNS and TLS — every one of those is an assumption the documentation made for you, silently, and every one of them breaks something specific.
What Actually Happens
The embedded outpost doesn’t register the forward-auth endpoint correctly. This isn’t a misconfiguration — it’s a confirmed bug, tracked across multiple open GitHub issues (#19839, #20458, #20631), and no amount of rechecking your provider settings fixes it. The fix is to stop using the embedded outpost entirely and deploy a dedicated proxy outpost container instead. If you’re seeing a 404 on the auth subrequest and everything else looks correct, this is almost certainly why.
Once that’s running, the next failure shows up as a 301 loop or a silent 403. If a tunnel sits between your web server and the outpost, $host resolves to the application’s domain on the way out, which the tunnel can’t match back to the outpost’s own hostname — hardcode the outpost hostname in that location block instead of trusting the variable. And if you’ve got existing security rules blocking dotfiles or hidden paths, check whether one of them is intercepting /outpost.goauthentik.io/ before it ever reaches the auth block. deny all returns a 403 with nothing in the error log, so you won’t see it unless you go looking.
The change that actually eliminated the rest of the cascade was routing the auth subrequest over a private mesh network directly between the web servers and the outpost containers, bypassing the public tunnel entirely for that one hop. Every Host header and SNI conflict in this setup traced back to a request crossing more proxies than it needed to. Removing one hop fixed problems that looked, individually, like four unrelated bugs.
The Architecture That Actually Works
One outpost container per cookie domain. Not per application — per cookie domain. Mix a single-application provider and a domain-level provider on the same outpost and the domain-level cookie scope leaks into every response that outpost serves, silently breaking sessions on sites that worked fine before you added the second provider. If you’re protecting a handful of standalone domains, that’s one outpost. If you’re also protecting *.yourdevdomain.com as a shared cookie domain, that’s a second, separate outpost on its own port — and Authentik enforces one provider per application either way, so subdomain-level access control inside a shared domain has to be done with expression policies, not separate applications.
Where Most People Get Stuck
“I configured everything correctly but the outpost returns 404.” — Confirmed embedded-outpost bug. Deploy a separate proxy outpost container and stop debugging the embedded one.
“I restarted NGINX and nothing changed.” — If proxy_pass uses a variable, NGINX resolves it via the resolver directive at startup and caches it. No resolver, or a stale one, means you’re still hitting the old IP. Pair variable-based proxy_pass with an explicit resolver, always.
“I get a 403 and there’s nothing in the error log.” — deny all doesn’t log. Add a debug header per location block (add_header X-Debug-Location "name" always;) to find out which block is actually handling the request before you waste an hour on the wrong one.
Where I Drew the Line
I went further than internal tooling and built an invitation-based enrollment flow on top of this for actual paying customers — invite issued by the application, account created by Authentik, a dedicated MFA stage with its own short validity window, then handoff back to the app. I did not, however, make Authentik the identity layer for that SaaS product’s general sign-up. A single self-hosted box going offline is a tolerable risk for internal tools. It’s not a tolerable risk to put between a paying customer and the product they signed up for. Application-layer auth stayed separate, with Authentik wired in afterward as one upstream option, not the only door.
Strategic Opposition Principle: The documentation describes what happens when nothing else is in the request path. Your infrastructure is the thing in the request path.
Related Field Notes: Authentik Forward Auth Broke Seven Different Ways Before It Worked, Your All-in-One Security Plugin Is a Single Point of Failure