Identity-Aware Proxies (Pomerium, Authentik Outpost, oauth2-proxy)
Identity-Aware Proxies (IAPs)
Section titled “Identity-Aware Proxies (IAPs)”When you self-host a community service that doesn’t natively support OIDC/SSO — say a URL shortener, a video host, or a metrics dashboard — you don’t have to expose it to the internet anonymously and hope for the best. You can put an identity-aware proxy (sometimes called a forward-auth proxy or zero-trust proxy) in front of it, and require users to authenticate against your existing IdP (e.g., Authentik) before any request reaches the upstream app.
This page covers what these tools are, when to choose which one, and how IrregularChat actually uses them.
What is an identity-aware proxy?
Section titled “What is an identity-aware proxy?”Browser ──TLS──> Cloudflare ──Tunnel──> IAP ──HTTP──> Upstream App │ └──OIDC──> Authentik (IdP)The IAP sits between your users and the protected app. For every request:
- IAP checks for a valid session cookie
- No cookie? Redirect to your IdP (Authentik) for login
- After IdP auth, IAP issues its own session cookie scoped to the protected app’s domain
- With a valid session, IAP forwards the request to the upstream — optionally injecting headers like
X-Forwarded-Useror a signed JWT so the upstream knows who’s calling
The upstream app doesn’t need to speak OIDC. It just needs to trust traffic from the IAP, optionally read identity headers, and ideally only be reachable through the IAP (not directly).
The main open-source options (May 2026)
Section titled “The main open-source options (May 2026)”| Tool | License | Footprint | Best for |
|---|---|---|---|
| Pomerium | Apache 2.0 | ~150-500MB RAM/instance (embeds Envoy) | Per-route policy, layer-4 TCP routes (RTMP), policy-as-code |
| Authentik Proxy Outpost | MIT | ~50-100MB RAM | You already run Authentik; want native integration with no separate config surface |
| oauth2-proxy | MIT | ~30-50MB RAM | Minimal Go binary; want generic OIDC client without vendor lock-in. Pin ≥7.15.2 (recent CVEs). |
| Authelia | Apache 2.0 | ~30MB RAM | Standalone (it’s also an IdP itself) — overlaps with Authentik if you already run it |
| Caddy + caddy-security | Apache 2.0 | tiny | Single-maintainer plugin; Trail of Bits found SSO flaws (2023) — vet carefully |
| Tinyauth | MIT | tiny | Newer, immature for production SSO |
| Ory Oathkeeper | Apache 2.0 | medium | API-gateway-shaped, overkill for most app-protection use cases |
When to pick what
Section titled “When to pick what”Already running Authentik? Default to Authentik Proxy Outpost. One config surface (Authentik admin UI), no separate OIDC client to wire up per app, ~10x lighter than Pomerium per protected app.
Need layer-4 TCP routing (RTMP ingest, raw socket forwarding)? Pomerium can do it; Authentik Proxy Outpost is HTTP/HTTPS only.
Need per-route or per-path policies beyond simple “must be in group X” — e.g., business-hours-only access, IP-range restrictions, custom claim expressions? Pomerium’s PPL is the most expressive language.
Want minimal moving parts and don’t need vendor-specific features? oauth2-proxy — boring, mature, fits in any reverse proxy chain.
How IrregularChat uses them today
Section titled “How IrregularChat uses them today”IrregularChat runs two Pomerium instances as the IAP layer in front of community apps that don’t natively OIDC:
| Pomerium instance | Apps protected | Auth hostname | Cookie domain |
|---|---|---|---|
peertube-pomerium | PeerTube, SearXNG, RTMP ingest | authenticate.irregularchat.com | irregularchat.com |
shlink-pomerium | URL shortener (url.irregular.chat) | authenticate.irregular.chat | irregular.chat |
Both authenticate via Authentik using the OIDC redirect flow.
Operator note: the rest of this page documents what we’ve learned the hard way running these. If you’re setting up your own IAP for a community app, the patterns here transfer to any of the IAP options above.
The architecture pattern that works
Section titled “The architecture pattern that works”videos.irregularchat.com ─┐search.irregularchat.com ─┼─> Pomerium ─> upstream appsrtmp.irregularchat.com ───┘ │ │ authenticate.irregularchat.com ─> AuthentikKey rules:
- Use a separate
authenticate.<parent>subdomain for the IAP’s own auth UI. Don’t reuse the same hostname as a protected app — that causes the IAP’s internal routes (/.pomerium/*,/oauth2/*) to collide with the upstream’s/, and users land on the IAP dashboard after login instead of the app they wanted. - Set
cookie_domainto the parent domain. A cookie set during auth onauthenticate.irregularchat.comis then visible tovideos.irregularchat.comandsearch.irregularchat.combecause they share the parentirregularchat.com. - One IAP instance per parent domain. Cookies cannot span unrelated parents.
irregular.chatandirregularchat.comare different parents — they need their own IAP instance each. - Whitelist exact callback URLs in your IdP for both the authenticate hostname and any other callback paths the IAP uses (
/.pomerium/callbackAND/oauth2/callbackfor Pomerium 0.32+).
Common pitfalls (and how to avoid them)
Section titled “Common pitfalls (and how to avoid them)”Pitfall 1: same-hostname all-in-one mode loses the original URL after auth
Section titled “Pitfall 1: same-hostname all-in-one mode loses the original URL after auth”Symptom: User logs in successfully but lands on /.pomerium/ (or /oauth2/sign_in) instead of the app they were trying to reach.
Cause: IAP is configured with authenticate_service_url set to the same hostname as a proxy route’s from: URL. The IAP’s internal routes claim / of that hostname.
Fix: dedicate a separate authenticate.<parent> subdomain for the IAP and set cookie_domain to the parent.
Pitfall 2: 5-minute access tokens cause re-login bounces every few minutes
Section titled “Pitfall 2: 5-minute access tokens cause re-login bounces every few minutes”Symptom: Users get bounced through the auth flow every 3-5 minutes, even though they just logged in.
Cause: IdP’s default access token validity is too short (Authentik defaults to 5 min). The IAP refreshes the token before expiry; any single network hiccup or IdP latency loses the session.
Fix: in Authentik, set the OAuth2Provider’s access_token_validity to something generous like hours=24. The access token is exchanged only between IAP and IdP — never exposed to users — so a long TTL doesn’t widen the user-visible attack surface. End-user session length is governed by the IAP’s own cookie TTL (cookie_expire), not the IdP token.
Pitfall 3: Client secrets with shell-special chars get silently dropped
Section titled “Pitfall 3: Client secrets with shell-special chars get silently dropped”Symptom: Authentik logs Invalid client secret, IAP logs oauth2: invalid_client. The secret is in your .env file but the container doesn’t see it.
Cause: Docker Compose’s env_file: parser silently drops values containing characters it can’t parse — backticks, dollar signs, parens, quotes, brackets, semicolons. Authentik’s default secret generator (generate_key(128)) produces such characters.
Fix: use only alphanumeric secrets for anything passed through env files. In Authentik, regenerate with generate_id(64) instead — produces base62 ([A-Za-z0-9]) only.
Pitfall 4: AppArmor blocks Envoy in Pomerium containers
Section titled “Pitfall 4: AppArmor blocks Envoy in Pomerium containers”Symptom: Pomerium container crash-loops with unable to bind domain socket ... errno=9.
Cause: Default Docker apparmor=docker-default profile permits Unix stream sockets but denies Unix dgram sockets. Envoy’s primary→child IPC needs dgram. Same class of bug bites Outline (Node.js spawn) and other containers.
Fix: add to your docker-compose.yml:
security_opt: - apparmor:unconfined - no-new-privileges:truePitfall 5: image: pomerium/pomerium:latest doesn’t auto-update
Section titled “Pitfall 5: image: pomerium/pomerium:latest doesn’t auto-update”Symptom: Running months-behind versions because the latest tag is only checked when you explicitly docker compose pull.
Fix: add a quarterly checklist item to docker compose pull <iap-service> && docker compose up -d <iap-service> for each protected app. Most security fixes ship as bundled Envoy bumps in routine point releases — the GitHub Security Advisories page alone misses them.
Pitfall 6: In-memory session store wipes on restart
Section titled “Pitfall 6: In-memory session store wipes on restart”Symptom: All users get a “please sign in” prompt right after you restart the IAP container.
Cause: Most IAPs default to in-memory session storage. Restart = sessions lost.
Fix: for production, use a persistent session store. Pomerium supports Postgres (Redis is not supported, despite some older blog posts). oauth2-proxy supports Redis. Authentik Proxy Outpost reuses Authentik’s existing Redis. Persistent storage is also a prerequisite for running multiple IAP replicas behind a load balancer (HA).
Authentik-specific tips
Section titled “Authentik-specific tips”If you’re using Authentik as your IdP (which is what IrregularChat does):
- Add the
groupsscope mapping to your OAuth2Provider if you want the IAP to gate access by group membership. The default OIDC scopes (openid email profile) don’t include groups. - Request
groupsin your IAP’s OIDC scope list explicitly:idp_scopes: ["openid", "email", "profile", "groups"] - Brand-aware redirects — Authentik supports multiple Brands, one per hostname. Users hitting
sso.irregularchat.comwill see IrregularChat-branded login; the same Authentik instance can serve other community brands on different hostnames. - Token validity per provider — different services may want different
access_token_validity. Pomerium-fronted apps can run athours=24safely. Mail providers (SOGo) work better athours=1due to client refresh patterns. API-only clients can stay at theminutes=5default.
Operational tips that apply to any IAP
Section titled “Operational tips that apply to any IAP”-
Run a synthetic auth-flow probe in monitoring per protected app, not just an “is the container up” health check. Phantom OIDC credentials (a deleted IdP application) won’t show up in container health — only when a user actually tries to log in.
Terminal window curl -s -o /dev/null -w 'HTTP %{http_code}\n' \"https://sso.irregularchat.com/application/o/<slug>/.well-known/openid-configuration"# 200 = OK, 404 = client deleted/renamed -
Document your IAP’s
cookie_domainstrategy — future-you will appreciate knowing which apps share an IAP because they share a parent. -
Test failure mode visibly — log out from the IdP, then try to access a protected app. You should land on the IdP login, not a vague proxy error. If the failure mode is a 500 instead, your IAP isn’t handling token expiration gracefully.
-
Keep public paths public — don’t gate static assets, healthcheck endpoints, or video streaming chunks behind auth. PeerTube needs
/static/streaming-playlists/,/socket.io/,/tracker/socket, and/client/to be unauthenticated; only the main UI needs OIDC. List public-prefix routes BEFORE the catch-all in your IAP config.
Migrating between IAPs
Section titled “Migrating between IAPs”You don’t have to commit forever. The IAPs above all interoperate with the same IdP (Authentik), so migrating from Pomerium → Authentik Proxy Outpost (or vice versa) is a configuration change, not a re-architecture. Things to plan for:
- Per-route policies translate to “Application → Policy Bindings” in Authentik. PPL features Pomerium has that Authentik Outpost doesn’t include time-of-day, IP-range, regex path matching, and layer-4 TCP — keep Pomerium for the routes that need them, even if you migrate the rest.
- Identity headers are named differently — Pomerium emits
X-Pomerium-*, Authentik emitsX-Authentik-*. Upstream apps reading these headers (rare) will need a one-line change. - Redirect URIs need updating in your IdP allow-list.
- Cookie continuity doesn’t transfer — users will re-authenticate once during the cutover.
Further reading
Section titled “Further reading”- Pomerium docs
- Authentik Proxy Outpost docs
- oauth2-proxy docs
- Cloudflare Tunnel + IAP guide
- Authentik installation guide