Authentik Proxy Outpost on Kubernetes: The Parts Nobody Documents
April 2026 — Every Authentik tutorial covers OIDC. Here’s what to do when the app has no OIDC support at all.
I run Authentik as my SSO provider on a self-hosted RKE2 cluster. Most apps I deploy support OIDC natively — you register an application in Authentik, it hands out tokens, life is good. But some apps don’t speak OIDC. n8n’s community edition is one. So is anything where you want to bolt authentication onto a bare internal tool without touching its config.
Authentik has a second auth mode for this: the proxy outpost with forward auth. Nginx intercepts every request, asks Authentik “is this user authenticated?”, and either passes the request through or redirects to login. The app never sees an auth challenge. It sounds straightforward. It is not, and the documentation leaves out the parts that will actually burn your time.
This post is those parts.
The two auth models — why this matters
When an app supports OIDC natively, the flow is: user hits app → app redirects to Authentik → Authentik issues a token → app validates the token. Authentik is the identity provider; the app is the relying party. You configure one thing in Authentik (an OAuth2/OIDC provider) and one thing in the app (client ID, secret, callback URL).
The proxy outpost model is different. Authentik sits in front of the app. Nginx uses its auth_request directive to call Authentik on every incoming request. Authentik checks whether the request has a valid session cookie. If yes, it returns 200 and sets X-authentik-* headers. If no, it returns 401 and nginx redirects the user to login. The app receives only authenticated requests, plus those headers identifying the user.
The app never participates in authentication. It doesn’t need to know about Authentik at all.
The embedded outpost trap
Here’s the first thing that will waste your afternoon.
Authentik ships with an embedded outpost — a proxy outpost that runs inside the Authentik server pod itself, always on. You don’t need to deploy anything extra. Sounds great. The catch: the embedded outpost has no providers assigned to it by default. When nginx calls https://app.djieno.com/outpost.goauthentik.io/auth/nginx and that request routes to the embedded outpost, the embedded outpost has no idea which application you’re talking about. It returns 404. Nginx converts 404 from an auth endpoint to 500 for the original request. Your app is just broken.
There are two ways out: assign your proxy provider to the embedded outpost in Authentik’s admin UI, or deploy a separate external proxy-outpost pod and route to that instead. I went with the external outpost — it’s cleaner for multi-app setups and avoids side-loading configuration onto the embedded outpost that you’ll forget about later.
The callback URL mismatch
If you try to use the embedded outpost anyway (or you accidentally configure your nginx annotations to point at authentik.djieno.com instead of your app domain), you’ll hit a second bug that’s harder to diagnose.
The proxy provider has an external_host field. This must match the hostname where your app lives — https://app.djieno.com. When a user logs in, Authentik sends them to https://app.djieno.com/outpost.goauthentik.io/callback to complete the flow. If the embedded outpost handles that callback but the callback goes to authentik.djieno.com, the OAuth2 state won’t match the provider’s external_host, and the user lands on the Authentik home page instead of the app. No error. Just a confusing redirect.
The fix: the /outpost.goauthentik.io path must be handled at the app’s hostname, not the Authentik hostname. That means the app’s ingress (or a second ingress on the same hostname, in the same namespace) has to route that path to the outpost.
Why ExternalName services exist for this
nginx ingress can only route to services in the same namespace as the Ingress resource. The app runs in n8n-prd. The external proxy-outpost pod runs in authentik-prd. You can’t directly reference a service across namespaces in an Ingress backend.
The solution is a Kubernetes ExternalName service. This is a service with no selector that resolves to a DNS name — it’s just an alias. You create one in n8n-prd that points at the outpost’s cluster-internal DNS name in authentik-prd. Then you create an Ingress in n8n-prd that routes /outpost.goauthentik.io to this alias service.
ExternalName service (outpost-svc.yaml in n8n-prd):
apiVersion: v1
kind: Service
metadata:
name: authentik-proxy-outpost
namespace: n8n-prd
spec:
type: ExternalName
externalName: ak-outpost-proxy-outpost.authentik-prd.svc.cluster.local
ports:
- port: 9000
targetPort: 9000
The ak-outpost-proxy-outpost name comes from whatever you named the outpost in Authentik. Check kubectl get svc -n authentik-prd to confirm.
The TLS cert problem you’ll hit if you route wrong
There’s a tempting shortcut: add the app hostname to the ingress in authentik-prd and handle everything there. Don’t. The TLS secret for app.djieno.com lives in n8n-prd (or gets issued by cert-manager into that namespace). If nginx tries to serve app.djieno.com from the authentik-prd ingress, it falls back to the default self-signed cert. Your browser will complain. Your auth flow will break in confusing ways.
Keep every ingress for app.djieno.com in the namespace where the TLS secret lives. That’s the app namespace.
Server-snippets are disabled
If you’re running the hardened nginx ingress that ships with RKE2, the following annotations are disabled:
nginx.ingress.kubernetes.io/server-snippetnginx.ingress.kubernetes.io/configuration-snippetnginx.ingress.kubernetes.io/auth-snippet
A lot of older guides and Stack Overflow answers use these to set up forward auth. They won’t work. The approach I’m describing here — auth-url and auth-signin annotations combined with a separate ingress for the outpost path — doesn’t need them.
The full working setup
Authentik side
- Create a Proxy Provider (type: Forward single application)
- Set
external_hosttohttps://app.djieno.com - Create an Application and assign the provider
- Create an access policy (e.g. bind an admin group if this is internal-only)
- Go to Outposts, find your external proxy-outpost, edit it, and add the new provider. Do not use the embedded outpost.
Kubernetes side
Three resources in the app namespace, plus annotations on the existing app ingress.
ExternalName service:
apiVersion: v1
kind: Service
metadata:
name: authentik-proxy-outpost
namespace: n8n-prd
spec:
type: ExternalName
externalName: ak-outpost-proxy-outpost.authentik-prd.svc.cluster.local
ports:
- port: 9000
targetPort: 9000
Outpost path ingress — handles /outpost.goauthentik.io/* on the app hostname. No TLS section needed here; the primary app ingress already handles TLS for this hostname and nginx merges the rules:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: n8n-outpost-path
namespace: n8n-prd
spec:
ingressClassName: nginx
rules:
- host: "app.djieno.com"
http:
paths:
- path: /outpost.goauthentik.io
pathType: Prefix
backend:
service:
name: authentik-proxy-outpost
port:
number: 9000
App ingress annotations — on the primary app ingress, add:
nginx.ingress.kubernetes.io/auth-url: "https://app.djieno.com/outpost.goauthentik.io/auth/nginx"
nginx.ingress.kubernetes.io/auth-signin: "https://app.djieno.com/outpost.goauthentik.io/start?rd=$scheme://$best_http_host$request_uri"
nginx.ingress.kubernetes.io/auth-response-headers: "Set-Cookie,X-authentik-username,X-authentik-groups,X-authentik-email,X-authentik-name,X-authentik-uid"
The auth-url and auth-signin both point at the app hostname, not authentik.djieno.com. This is the thing that routes correctly through the second ingress to the proxy-outpost via ExternalName.
What the app receives
After a successful auth check, nginx forwards the request to the app with these headers set by the outpost:
X-authentik-usernameX-authentik-emailX-authentik-groupsX-authentik-nameX-authentik-uid
The app can use these if it wants, or ignore them entirely. The authentication gate is fully enforced at the nginx layer regardless. Unauthenticated requests never reach the app pod.
One note: if the app has its own login page (n8n does), users will still see it after passing the SSO gate. That’s a separate UX issue — the nginx auth gate and the app’s internal auth are independent. The nginx gate ensures only authenticated users can reach the app at all; what the app does with those users after that is up to it.
The request flow
Browser → https://app.djieno.com/
→ nginx ingress (n8n-prd namespace)
→ auth_request to /outpost.goauthentik.io/auth/nginx
→ matched by outpost-path ingress
→ ExternalName service → ak-outpost-proxy-outpost.authentik-prd.svc.cluster.local:9000
→ 401: redirect to /outpost.goauthentik.io/start?rd=...
→ 200: forward to app pod with X-authentik-* headers
Summary
The working pattern:
- Deploy an external proxy-outpost in Authentik. Assign your proxy provider to it, not to the embedded outpost.
- Set
external_hoston the provider to match the app URL, not the Authentik URL. - In the app namespace, create an ExternalName service pointing at the outpost’s ClusterDNS address.
- In the app namespace, create a second Ingress routing
/outpost.goauthentik.ioon the app hostname to that ExternalName service. - Add
auth-urlandauth-signinannotations to the primary app ingress, both pointing at the app hostname. - Keep TLS termination on the primary app ingress in the app namespace. Don’t touch the Authentik namespace.
Everything in the app namespace, no server-snippets, no cross-namespace ingress hacks. Once it clicks, it’s replicable across every app that needs SSO without native OIDC support.


