Guide
Single sign-on for self-hosters: the honest state of it
Half the tools you run cannot participate in single sign-on at all. This is what the three mechanisms really give you, which stack to run at home and at work, and the two failure modes nobody warns you about.
How do you set up single sign-on for self-hosted apps?
There are three mechanisms and they are not equivalent. Forward auth makes your reverse proxy ask an auth server before passing a request, which gates access but does not log you into the app, so you often log in twice. Trusted header auth makes the app accept an identity from a proxy-injected header, which does log you in but is a complete compromise if anything can reach the app directly. Native OIDC is the only one that ends up as a single identity, and only about half of self-hosted tools with user accounts support it.
Single sign-on in a self-hosted stack is not one technology. It is three, they behave completely differently under failure, and the marketing language treats them as interchangeable. Getting this wrong produces the two outcomes people actually report: logging in twice to everything, or an unauthenticated hole they did not know they made.
The three mechanisms, and what each one really gives you#
Forward auth: gates the request, does not log you in#
Your reverse proxy intercepts the request and asks an auth server whether this session is permitted. Authelia serves /api/authz/forward-auth for Traefik, Caddy and HAProxy, /api/authz/auth-request for nginx and /api/authz/ext-authz for Envoy. If the answer is yes, the request continues. If no, you are redirected to a login portal.
What this buys you: an app with no authentication of its own becomes unreachable to strangers, with two-factor in front of it. That is real and valuable.
What it does not buy you: the app still does not know who you are. Unless it reads Remote-User and Remote-Groups, you authenticate at the portal, pass, and then meet its own login form. Two logins.
The second problem is worse. Forward auth is all-or-nothing per path, so anything that is not a browser needs an exception: /api/ for a mobile app, /remote.php/dav for Nextcloud desktop clients, entire subdomains for Jellyfin on a TV. Every policy: bypass you write is an unauthenticated hole, and Authelia's access control rules are ordered with first match winning, so one bypass placed too high silently disables two-factor for everything below it. Audit the whole rule list, not the rule you just added.
There is also a boring blocker that stops people on day one. The session cookie must be set on a domain that is a parent of every protected app, and browsers enforce the Public Suffix List, so you cannot share a cookie between example.duckdns.org and example-auth.duckdns.org. Forward auth requires a registrable domain you own.
Trusted header auth: logs you in, trusts anything#
The proxy authenticates you and injects a header. The application reads it and treats the value as a logged-in identity, creating the account if it does not exist. Navidrome, Frigate and Firefly III all work this way.
This solves the double-login problem completely. It also means the application's entire security model is "I believe whatever the last hop told me". If anything can reach that application other than your proxy, then a single request settles it:
curl -H 'Remote-User: admin' http://10.0.0.20:4533/Another container on the same Docker network can do that. So can any LAN device, if you published the port. The mitigation is not a setting inside the app, it is network topology: put the app on an internal Docker network with the proxy, publish nothing, and have the app refuse connections that did not come from the proxy's address.
Header auth is right for exactly one situation: an app whose network path you control completely, with no OIDC support, where a double login is unacceptable. It is not a default.
Native OIDC: the only one that ends as one identity#
The application redirects you to your provider, gets a signed ID token back, and matches or creates its own user record from the claims. You log in once. Group claims can drive roles. It survives native clients, because the client does the same redirect. It is the only mechanism that produces the thing people mean when they say single sign-on.
The cost is that the application has to support it, which is where the subject falls apart.
The gap, measured#
Of the 90 profiled tools that have user accounts at all, 50.0 percent ship native OIDC and 31.1 percent have nothing you can connect an identity provider to: local accounts only, or a proxy in front and nothing else. Another slice is LDAP-only or depends on a third-party plugin whose maintenance you do not control. Jellyfin's OIDC plugin repository was archived by its original author in May 2026 and is now carried by a community fork, which is a good illustration of what "supported" means in this category.
| Tool | Category | Identity | Note |
|---|---|---|---|
| Actual Budget | Productivity | Native OIDC | OpenID or OAuth2 login is built in and marked preview, configured in `config.json` or the admin UI; it is also the prerequisite for multi-user. |
| Audiobookshelf | Media | Native OIDC | Built-in OpenID Connect with an auto-populate button that reads the provider's discovery document; no plugin needed, and the mobile apps support the OIDC flow too. |
| Beszel | Monitoring | Native OIDC | PocketBase OAuth2 and OIDC providers are built in, password auth can be turned off with `DISABLE_PASSWORD_AUTH`, and `TRUSTED_AUTH_HEADER` supports forwarded auth. |
| BookStack | Documents | Native OIDC | OIDC, SAML2 and LDAP are all first-party and configured with environment variables, including group to role sync; no plugin and no reverse proxy trickery required. |
| Coolify | Platforms | Native OIDC | OAuth providers have been supported for some time and OpenID Connect login landed in the 4.4 release candidate; check your version before promising SSO to anyone. |
| Forgejo | Developer | Native OIDC | OAuth2/OIDC and LDAP are built-in authentication sources in the admin UI, no plugin needed, and you can disable local registration entirely. |
| FreshRSS | Feeds | Native OIDC | Form login, HTTP Basic, reverse-proxy headers (`Remote-User` or `X-WebAuth-User` from a CIDR in `trusted_sources`) and OpenID Connect are all supported in core, with auto-registration of new users on first proxy login. |
| Gatus | Monitoring | Native OIDC | Built-in OIDC under `security.oidc` with an issuer URL and an allowed-subjects list, plus basic auth as the simpler option. |
| GitLab CE | Developer | Native OIDC | OmniAuth ships in the free self-managed build with OIDC, SAML, LDAP and social providers configured in `gitlab.rb`; LDAP group sync and multiple LDAP servers are Premium. |
| Gitea | Developer | Native OIDC | OAuth2/OIDC and LDAP are built-in authentication sources configured from the admin panel, with optional auto-provisioning of accounts. |
| Gotify | Communication | Native OIDC | OIDC login is built in from v3, with group claims mapped to user and admin roles and an optional auto-redirect; before v3 it was local accounts only. |
| Grafana | Monitoring | Native OIDC | Generic OAuth2 and OIDC plus named providers (Google, GitHub, Azure AD, Okta, Keycloak) are in the free build; SAML, LDAP team sync and role-based access control are Enterprise. |
| Harbor | Developer | Native OIDC | OIDC and LDAP/AD are first-class auth backends configured in the admin UI, with OIDC group claims mapped onto Harbor project roles; switching auth mode after users exist is one-way. |
| Headscale | Networking | Native OIDC | Built-in OIDC: point it at Authentik, Keycloak or Google and node registration goes through your IdP instead of pre-auth keys. |
| HedgeDoc | Documents | Native OIDC | Generic OAuth2/OIDC via the `CMD_OAUTH2_*` variables plus LDAP, SAML, GitHub, GitLab and Google; local email sign-in also exists and is on by default. |
| Homarr | Dashboards | Native OIDC | Credentials, OIDC and LDAP are all first-class in the box, selected through the auth provider environment variables, with per-user and per-group board permissions. |
| Homepage | Dashboards | Native OIDC | v2.0 added a built-in gate (`HOMEPAGE_AUTH_ENABLED`) with either a single shared password or OIDC, but it grants access to any identity your provider authorizes for the client and applies no claim or group checks of its own. |
| Immich | Photos and files | Native OIDC | OIDC is built into the server with claim-based storage quota and role mapping, but local password login stays on until you explicitly disable it in admin settings. |
| Karakeep | Feeds | Native OIDC | Any OIDC-compliant provider via `OAUTH_WELLKNOWN_URL`, `OAUTH_CLIENT_ID` and `OAUTH_CLIENT_SECRET`, with `DISABLE_PASSWORD_AUTH` and `OAUTH_AUTO_REDIRECT` to make it the only path; the callback is `/api/auth/callback/custom`. |
| Kavita | Media | Native OIDC | Native OpenID Connect landed in v0.8.8 and can run alongside or fully replace local accounts, including mapping provider roles to library access and age restrictions. |
| Keycloak | Identity | Native OIDC | Keycloak is the issuer, and it federates upward too: identity brokering to other OIDC and SAML providers, plus LDAP and Kerberos user federation with configurable write-back. |
| Komodo | Platforms | Native OIDC | Built-in OIDC plus GitHub and Google OAuth, configured through `KOMODO_OIDC_*` environment variables with PKCE support. |
| Mattermost | Communication | Native OIDC | OIDC and SAML are compiled into the binary but unlock only with a license key; from v11 Team Edition lost even GitLab SSO, so free self-hosters get local accounts. |
| Mealie | Productivity | Native OIDC | Native OIDC with discovery, admin and group claim mapping, and `ALLOW_PASSWORD_LOGIN=false` to hide the local login form entirely. |
| Miniflux | Feeds | Native OIDC | Generic OIDC and Google are built in via `OAUTH2_PROVIDER`, plus header-based proxy auth through `AUTH_PROXY_HEADER` (which requires `TRUSTED_REVERSE_PROXY_NETWORKS`), and WebAuthn passkeys for local accounts. |
| Open WebUI | Local AI | Native OIDC | Generic OIDC and several named OAuth providers are built in, with role and group claim mapping, but `WEBUI_URL` must be set correctly first or the redirect fails. |
| Outline | Documents | Native OIDC | Generic OIDC plus Google, Slack, Microsoft Entra and Discord are built in, and at least one of them is mandatory: there is no local username and password login at all. |
| Paperless-ngx | Documents | Native OIDC | OIDC through django-allauth, configured as a JSON blob in `PAPERLESS_SOCIALACCOUNT_PROVIDERS` plus an entry in `PAPERLESS_APPS`; group sync from claims works, and a `Remote-User` header mode exists separately. |
| PhotoPrism | Photos and files | Native OIDC | OpenID Connect login is listed as available in the free Community Edition, but the User Management web UI that makes multi-user setups pleasant is a Plus feature. |
| Pocket ID | Identity | Native OIDC | It is the OIDC issuer, OpenID Certified, and it can sync users and groups inward from an LDAP directory. There is no SAML, no upstream social login and no password login. |
| Portainer | Platforms | Native OIDC | CE supports LDAP and generic OAuth login; Active Directory, provider templates and group-to-team mapping are Business Edition only. |
| Proxmox VE | Platforms | Native OIDC | Realms include OpenID Connect, LDAP and Active Directory natively, plus built-in TOTP and WebAuthn two-factor. |
| Synapse | Communication | Native OIDC | `oidc_providers`, SAML2 and CAS are configured directly in homeserver.yaml, but Element's strategic path is now the separate Matrix Authentication Service. |
| Technitium DNS Server | Networking | Native OIDC | OpenID Connect single sign-on with scope and group-to-role mapping shipped in v15 (April 2026); before that it was local accounts only. |
| Trilium Notes | Documents | Native OIDC | Direct OpenID Connect and TOTP support are built into the server for login; there is no multi-user model, so SSO here authenticates you, it does not partition content. |
| TrueNAS Community Edition | Platforms | Native OIDC | Active Directory and LDAP directory services, plus OIDC-based identity provider support in the recent releases for web UI login. |
| Unraid | Platforms | Native OIDC | Unraid 7.2 added optional OIDC single sign-on to the WebGUI via the built-in Unraid API; before that it was a single root password. |
| Vaultwarden | Identity | Native OIDC | OIDC login is built in since 1.35.0, but a master password is still required and is not controlled by the identity provider, because it derives the vault key. |
| Vikunja | Productivity | Native OIDC | Built-in OpenID Connect against any provider, plus LDAP added in 1.0.0; no plugin needed. |
| Wiki.js | Documents | Native OIDC | Generic OpenID Connect plus a long list of first-party strategies (Azure AD, Google, GitHub, Keycloak, Auth0, LDAP, SAML), all configured in the admin UI rather than in environment variables. |
| Woodpecker CI | Developer | Native OIDC | There are no local accounts at all: login is OAuth against the configured forge (Forgejo, Gitea, GitHub, GitLab or Bitbucket), so your forge is your identity provider and there is no generic OIDC option. |
| authentik | Identity | Native OIDC | It is the identity provider, and it can also federate upward: OAuth, SAML, LDAP and Kerberos sources let you chain an existing directory or social login behind it. |
| linkding | Feeds | Native OIDC | `LD_ENABLE_OIDC` turns on OpenID Connect and `LD_ENABLE_AUTH_PROXY` plus `LD_AUTH_PROXY_USERNAME_HEADER` covers Authelia-style header auth; note OIDC matches users by email address, not username. |
| mailcow: dockerized | Communication | Native OIDC | Identity providers are configured in the mailcow UI itself: Keycloak, generic OIDC and LDAP, with automatic user creation on first login; mail clients still need app passwords unless you use the Mailpassword flow. |
| wg-easy | Networking | Native OIDC | Local admin accounts with optional TOTP by default; OAuth/OIDC external authentication landed in 15.4.0 (August 2026). |
| Home Assistant | Home automation | OIDC via plugin | No official OIDC provider; people use the HACS custom component hass-oidc-auth or an auth proxy, and the companion mobile apps handle proxy logins badly. |
| Jellyfin | Media | OIDC via plugin | An official LDAP plugin ships in the plugin catalog; OIDC comes from the third-party SSO plugin, whose original repo (9p4) was archived in May 2026 and is now carried by a community fork. |
| Nextcloud | Photos and files | OIDC via plugin | LDAP and SAML integration ship as bundled apps and OIDC comes via the maintained `user_oidc` app; all of it is app-store surface that has to survive every major upgrade. |
| Node-RED | Home automation | OIDC via plugin | `adminAuth` in settings.js accepts local users or any Passport strategy, so OAuth2 and OIDC work through an installed strategy module rather than a built-in provider. |
| Seafile | Photos and files | OIDC via plugin | OAuth/OIDC and basic LDAP are configurable in the Community Edition; SAML, ADFS, Shibboleth and LDAP group sync are Professional Edition features. |
| Wazuh | Security | OIDC via plugin | SAML single sign-on to the dashboard through the indexer's security plugin, with documented setups for Entra ID, Okta, Keycloak and authentik; the docs cover SAML rather than OIDC. |
| Dozzle | Monitoring | Trusted proxy header | Either a bcrypt `users.yml` file for local accounts, or forward-proxy mode trusting `Remote-User`, `Remote-Email` and `Remote-Name` from Authelia, oauth2-proxy or Cloudflare Access. No native OIDC. |
| File Browser | Photos and files | Trusted proxy header | Authentication is local JSON accounts, a proxy header method that trusts an upstream-injected username, a command hook, or no auth at all; there is no OIDC and sessions are unrevocable JWTs. |
| Firefly III | Productivity | Trusted proxy header | `AUTHENTICATION_GUARD=remote_user_guard` trusts a proxy-injected header (`REMOTE_USER` by default) for Authelia and similar; LDAP support was dropped and there is no built-in OIDC. |
| Frigate | Home automation | Trusted proxy header | Built-in users with PBKDF2 hashing, or disable that and trust `X-Forwarded-User` and `X-Forwarded-Groups` from Authelia or Authentik in front. |
| Navidrome | Media | Trusted proxy header | Reverse proxy authentication via ND_REVERSEPROXYUSERHEADER (default Remote-User) with an ND_REVERSEPROXYWHITELIST CIDR; there is no native OIDC, and Subsonic clients still authenticate with a Navidrome password regardless. |
| grocy | Productivity | Trusted proxy header | `AUTH_CLASS` swaps the auth middleware: local users by default, `ReverseProxyAuthMiddleware` reading a `REMOTE_USER` header, or `LdapAuthMiddleware`. No OIDC. |
| Authelia | Identity | LDAP only | Authelia is the identity provider; its own user source is a YAML file of Argon2id hashes or an LDAP directory, and it cannot federate up to an external OIDC or social provider. |
| Calibre-Web | Media | LDAP only | LDAP works, plus OAuth against GitHub and Google specifically, plus a reverse proxy header login mode; there is no general OIDC, so most people use forward auth and enable the proxy header option. |
| Emby | Media | LDAP only | An LDAP plugin exists and works; there is no first-party OIDC, so anything IdP-shaped means a forward-auth proxy in front of the web UI, which native clients then bypass. |
| LLDAP | Identity | LDAP only | It is the LDAP server and nothing else. No OIDC in either direction, so by itself it gives you one password everywhere, not one session. |
| Stalwart | Communication | LDAP only | Community authenticates against an internal directory, LDAP/Active Directory or SQL; signing in through a third-party OIDC provider such as Keycloak or Authentik is an Enterprise feature. |
| ESPHome | Home automation | Auth proxy only | The Device Builder dashboard has no user model of its own; as a Home Assistant app it inherits ingress auth, standalone it needs a proxy in front. |
| Jellyseerr | Media | Auth proxy only | Login delegates to your media server's accounts (Jellyfin, Emby or Plex) plus local users; native OpenID Connect is still only in a preview branch rather than a stable release, so IdP integration means forward auth in front. |
| Netdata | Monitoring | Auth proxy only | The local agent dashboard has no login of its own; identity comes either from signing in to Netdata Cloud or from an authenticating proxy you put in front of 19999. |
| Prometheus | Monitoring | Auth proxy only | No user accounts at all. Basic auth and TLS can be set in `web.config.yml`; anything with identity in it means an auth proxy in front. |
| Scrutiny | Monitoring | Auth proxy only | There is no login at all on the web UI or the API, so anything resembling access control has to be an authenticating proxy in front of port 8080. |
| Traefik | Networking | Auth proxy only | No user accounts. The dashboard is unauthenticated unless you put a BasicAuth or ForwardAuth middleware in front of it, which is also how you bolt Authelia onto backends. |
| code-server | Developer | Auth proxy only | Authentication is a single shared password in `~/.config/code-server/config.yaml`; there are no user accounts, so anything more than one person means an auth proxy in front. |
| wallabag | Feeds | Auth proxy only | Local accounts with optional two-factor by email or TOTP; there is no OIDC and no supported LDAP in core, so SSO means an auth proxy in front and a matching local user. |
| AdGuard Home | Networking | Local accounts only | Local users defined in AdGuardHome.yaml with bcrypt hashes, served over HTTP basic auth. No OIDC, no LDAP, no plugin path. |
| CasaOS | Platforms | Local accounts only | A single local account created at first boot, no LDAP, no OIDC, and no meaningful multi-user model. |
| Docmost | Documents | Local accounts only | The community edition has local accounts only; SAML 2.0, OIDC, LDAP, MFA and SCIM are all gated behind the paid Business or Enterprise tier and require a licence key. |
| Dokploy | Platforms | Local accounts only | Local accounts with two-factor and invitations; there is no OIDC login for the dashboard itself. |
| Duplicati | Backup | Local accounts only | Local password only, mandatory since 2.1.0. Proxy setups are supported through `DUPLICATI__WEBSERVICE_PRE_AUTH_TOKENS`, but there is no OIDC or LDAP. |
| Glance | Dashboards | Local accounts only | Local users only, defined in the `auth` block of the config with bcrypt hashes; there is no OIDC, no LDAP and no header trust, so put an auth proxy in front if you need real identity. |
| Kopia | Backup | Local accounts only | `kopia server` has its own username and password list plus optional TLS client certificates. There is no OIDC or LDAP; put it behind an authenticating proxy if you need identity. |
| MinIO | Backup | Local accounts only | External identity via OIDC and LDAP was removed from the community build in RELEASE.2025-05-24 and moved into the commercial product. What is left is local access keys and policies. |
| Nginx Proxy Manager | Networking | Local accounts only | Local accounts only, with per-user permissions and an audit log. No OIDC, no LDAP, and none is planned in the v2 line. |
| PLANKA | Productivity | Local accounts only | OIDC existed in the community edition until 2.2.0 removed it to PLANKA Pro; local accounts with optional TOTP are what remains. |
| Pi-hole | Networking | Local accounts only | A single web password with optional TOTP, plus generated application passwords for scripts. No user accounts, no OIDC, no LDAP. |
| Plex Media Server | Media | Local accounts only | Identity belongs to plex.tv: accounts, sharing and login all route through Plex's cloud, there is no way to point it at your own IdP, and managed users under Plex Home are the closest thing to a local account. |
| Prowlarr | Media | Local accounts only | Local forms or basic login only, with no LDAP or OIDC; the API key is the real credential, and it is the one the *arr apps use, so treat it like a shared secret. |
| Radarr | Media | Local accounts only | Local forms login only; v6 removed basic auth entirely, there is no LDAP or OIDC, and the API key bypasses the UI login for anything holding it. |
| Sonarr | Media | Local accounts only | Forms or basic login against a local user only, mandatory since v4; there is no LDAP or OIDC, so SSO means a forward auth proxy, and the API key bypasses it entirely for any client that has it. |
| Syncthing | Photos and files | Local accounts only | The web GUI has a single local username and password and no identity integration; if you expose it, put an authenticating proxy in front of 8384. |
| Uptime Kuma | Monitoring | Local accounts only | Local accounts with optional TOTP only; the OAuth support in the codebase authenticates monitored endpoints, not people logging in. |
| Zigbee2MQTT | Home automation | Local accounts only | The frontend has one shared `auth_token` in configuration.yaml, no user accounts and no OIDC, so put a proxy in front if it leaves your LAN. |
| n8n | Dashboards | Local accounts only | The self-hosted community build has local accounts only: SAML, OIDC and LDAP are all gated behind an Enterprise plan, so free self-hosters put an auth proxy in front or live with n8n's own user table. |
| ntfy | Communication | Local accounts only | Local users and per-topic ACLs managed with `ntfy access`; there is no OIDC or LDAP, so put an auth proxy in front of the web app if you need single sign-on. |
Read that table as an architecture document, not a scoreboard. Every row that is not oidc-native is a decision: forward auth and two logins, a trusted header and a network constraint, or a local account outside your identity system. There is no fourth option. The full analysis is in The single sign-on gap.
What to run at home#
For a household, the winning stack is the smallest one that covers your actual app list.
If every app you run speaks OIDC, use Pocket ID. It is a single Go binary, roughly 30 MB, passkey-only, and it passed the OpenID Foundation conformance suite in 2026. Setup is one container, one volume, APP_URL set to the exact external HTTPS URL and an ENCRYPTION_KEY from openssl rand -base64 32. It does nothing for apps without OIDC, and that limitation is the product rather than a gap.
Two things to know before you commit. WebAuthn credentials are bound to the relying party ID derived from APP_URL, so moving from id.home.lan to id.example.com invalidates every passkey your users registered. And WebAuthn requires a secure context, so a plain HTTP LAN deployment cannot register a credential at all. Decide the final domain and put a real certificate in front before you invite anyone. Reverse proxy and TLS covers that half.
If some of your apps have no auth at all, use Authelia plus your existing reverse proxy, and add LLDAP behind it if you want one password across the apps that only speak LDAP. Authelia at 35 MB plus LLDAP at 15 MB is a 50 MB identity layer, which is remarkable value. Be clear about what you are getting: forward auth for the no-auth apps, and an OIDC provider that the project itself still labels beta (Beta 8, targeted at 4.40.0) and that has shipped breaking changes inside minor releases.
Do not run authentik or Keycloak for a household. Both are excellent and both are the wrong size. authentik documents a 2 CPU core and 2 GB RAM minimum and spends roughly a gigabyte idle across three containers. Keycloak's container guide suggests 2 GB for the JVM alone. If your requirement is one OIDC issuer for six apps, that is a tenth of the memory and a hundredth of the concepts away.
| Pocket ID | Authelia | LLDAP | authentik | Keycloak | |
|---|---|---|---|---|---|
| Idle RAM | 30 MB | 35 MB | 15 MB | 1000 MB | 750 MB |
| Containers | 1 | 1 (2 with Redis) | 1 | 3 | 2 |
| OIDC | yes, certified | yes, beta | no | yes | yes |
| SAML | no | no | no | yes | yes |
| Forward auth | no | yes | no | yes | no |
| LDAP server | consumes only | consumes only | yes | outpost | federates |
| Ops load | 1/5 | 3/5 | 1/5 | 4/5 | 4/5 |
| Licence | BSD-2-Clause | Apache-2.0 | GPL-3.0 | MIT core, licensed enterprise dir | Apache-2.0 |
What to run for a small team#
Here the answer flips, because the requirements change from "one login for my family" to "onboarding, offboarding, an audit trail and one stubborn vendor app that only speaks SAML".
Run authentik. It is the only option in this category that covers all four integration styles in one product: native OIDC, SAML, an LDAP outpost for apps that speak nothing else, and a proxy provider for apps with no auth at all. The gigabyte of RAM is a rounding error on a business server, and one admin UI showing who logged into what is worth real money when someone leaves.
Two operational notes. Since 2025.10 Redis is gone, with caching and sessions moved into PostgreSQL: budget about 50 percent more database connections and clear the orphaned container on upgrade. Since 2026.8 the server ignores X-Forwarded-* headers unless the connection comes from a network in AUTHENTIK_LISTEN__TRUSTED_PROXY_CIDRS. Set it before you pull the tag, or every login appears to come from your reverse proxy.
Choose Keycloak instead when you need realms as genuine tenant separation, token exchange, fine-grained authorization policies, or something an auditor already recognizes. Accept the cost: every version migrates the schema on first start with no reverse migration, so a rollback means restoring PostgreSQL rather than changing the image tag. And never run start-dev as a deployment: it uses a dev-file H2 database inside the container, there is no supported migration from it to PostgreSQL, and a docker compose down -v takes your realm with it. Keycloak vs authentik goes deeper on that fork in the road.
The account-linking trap#
This is the failure that catches almost everybody, and it happens on the day you switch a working app to OIDC.
The app already has a local user, alice, with three years of history. Alice logs in through your provider for the first time. The app receives a token with subject f47ac10b-... and email alice@example.com, finds no user matching that subject, and creates a second account. Alice is now looking at an empty library, an empty document list or a dashboard with no dashboards, and her real data belongs to an account she can no longer reach because you disabled password login an hour ago.
How different apps behave varies and you must check yours before you flip the switch:
- Some match on the email claim and link automatically. Convenient, and also an account takeover path if your provider does not verify email addresses.
- Some match on preferred username. Some match only on the OIDC subject and will never link an existing account without a manual database edit.
- Some, including Immich, leave local password login enabled after you configure OIDC until you disable it in admin settings, which gives you a window to test.
The safe procedure:
- Configure OIDC but leave local login enabled.
- Make the identity provider issue the exact email and username the local account already has.
- Log in through OIDC as a test user first and confirm you land on the existing account with its data, not a fresh one.
- Only then disable local password login, and only after you have a database backup you have restored once.
Do this in the wrong order across ten apps and you will spend a weekend merging users by hand in ten schemas.
The disaster case: your IdP is down#
Every app behind your identity provider is now inaccessible, and that set very likely includes the admin interface you would use to fix it. Put your proxy dashboard, container manager and monitoring behind the same forward auth and you have locked yourself out of the tools for diagnosing the lockout.
It gets more circular than that. If the provider is unreachable because DNS is broken and your DNS server's admin UI sits behind the provider, you are editing hosts files.
Write these down on installation day, not during the incident:
- Keep one route in that does not depend on identity. SSH with a key, or a WireGuard tunnel. Never gate SSH behind your web SSO.
- Keep a local admin account on every critical app, even after you enable OIDC. Yes, this weakens the "one identity" story. It is the correct trade.
- Know the recovery command. authentik:
docker compose run --rm server create_recovery_key 10 akadmin, which prints a link valid for ten minutes. Pocket ID: an admin-issued one-time access link, which means you need a second admin. Keycloak: a bootstrap admin via environment variables against the database. - Have a second admin with credentials on different hardware. For Pocket ID this is not optional: there is no password fallback, so if the only admin loses every enrolled passkey you are editing SQLite by hand.
- Do not put the identity provider behind its own forward auth. People do this. It works until it does not.
- Back up the encryption key separately from the database. Authelia's
storage.encryption_key, authentik'sAUTHENTIK_SECRET_KEY, Pocket ID'sENCRYPTION_KEYand LLDAP'sLLDAP_KEY_SEEDall live outside their databases. Restore a dump with a different key and every TOTP secret, passkey and stored credential in it is garbage.
What to do next#
Sort your actual app list into three piles using the table above: native OIDC, LDAP or plugin, and nothing. If the third pile is small, Pocket ID is your answer and an evening is enough. If it is large, you need forward auth, which means Authelia and a real domain, so read Reverse proxy and TLS first. Either way, create the break-glass account before you enable anything, and check A security baseline for a home server for the rest of the perimeter.
Questions#
What is the difference between forward auth and OIDC?
Forward auth happens in the reverse proxy: before serving a request it asks an auth server whether this session is allowed, and passes or blocks. The application never learns who you are, so it still shows its own login form unless it reads identity headers. OIDC happens in the application: it redirects you to the identity provider, receives a signed token back, and creates or matches a user account. Only OIDC produces one identity across your stack.
Is Authelia or Authentik better?
Different shapes. Authelia is a 35 MB Go binary that does forward auth well and has an OIDC provider the project still labels beta. authentik covers OIDC, SAML, an LDAP outpost and a proxy provider in one product, and costs about a gigabyte of RAM across three containers. Pick Authelia for a single host where forward auth is the main job; pick authentik when you need SAML or LDAP as well. See Authelia vs authentik.
Can you use SSO with Jellyfin or Plex?
Partially and not at all, respectively. Jellyfin has an official LDAP plugin and a third-party OIDC plugin, but the OIDC plugin only covers the web UI: native clients on a TV or phone still authenticate against Jellyfin's own user database. Plex Media Server routes all identity through plex.tv and there is no way to point it at your own provider. Media servers are the worst category for SSO and it is not close.
What happens if my identity provider goes down?
Everything behind it stops, including the tools you would use to fix it. Plan the break-glass path on installation day: a second admin account with credentials on different hardware, the documented recovery command written down (docker compose run --rm server create_recovery_key 10 akadmin for authentik), and at least one route to your server, usually SSH or a VPN, that does not depend on the identity provider being up.
Is header authentication safe?
Only if the application is genuinely unreachable except through the proxy. The app trusts a header such as Remote-User or X-Forwarded-User as proof of identity, so anyone who can send it a request directly, including another container on the same Docker network or any device on your LAN if the port is published, can name themselves your administrator with one curl. Bind those containers to the proxy network only, and never publish their ports.
Do I need LDAP if I have OIDC?
Only for the apps that speak nothing else. LLDAP is 15 MB and gives you one place to change a password, which is genuinely useful, but it is not single sign-on: there is no session concept, so every app still shows its own login form. It is a component you put behind Authelia, authentik or Keycloak, not a solution on its own.
How many self-hosted apps support OIDC?
Of the 90 profiled tools that have user accounts at all, 50.0 percent ship native OIDC and 31.1 percent have nothing you can connect an identity provider to. That second group is the one that shapes your architecture, because those apps need a proxy in front of them and will therefore always be a second login or a trusted header.
Sources#
- Authelia OpenID Connect 1.0 provider roadmap and beta status
- Authelia session configuration and cookie domains
- Authelia 4.38 release notes with breaking changes
- authentik Docker Compose installation and system requirements
- authentik release 2026.8, trusted proxy CIDRs
- authentik lockout recovery procedure
- Keycloak upgrading guide
- Keycloak configuration, dev versus production mode
- Pocket ID introduction and scope
- LLDAP project README, scope and limitations
Published . Last reviewed . Found something out of date? Tell us and we will fix it and log the change.