The SSO tax in self-hosted software
Open source apps that keep OIDC and SAML in a paid tier: why maintainers price it that way, and the checklist to run before you install anything.
What the SSO tax is
The SSO tax in self-hosted software is the pattern where the application is free and single sign-on (SSO) is the one feature you have to buy. You can run the whole thing on your own VPS with no licence key and no seat count. Then you open the authentication page in the documentation and find OpenID Connect (OIDC) or SAML (security assertion markup language) sitting under a paid plan.
This matters more than an ordinary paywalled feature, because SSO is what makes a set of self-hosted services behave like one system. An identity provider (IdP) gives you one account per person, one password policy, one place to switch on multi-factor authentication (MFA), and one place to shut someone off. Without it, every app keeps a small user database of its own, and you maintain each one by hand.
The pattern is old enough to have a public scoreboard. The SSO Wall of Shame at sso.tax lists vendors that charge a large premium for single sign-on, with entries going back to 2018, and its author sets a fair bar: "If your SSO support is a 10% price hike, you're not on this list." Most of that list is closed software. The same pricing logic now appears in open source projects that you host yourself.
Why maintainers put single sign-on behind a paid tier
There are two reasons and both are honest. SSO is expensive to support, and it is one of the few features a large organisation will pay for.
The support cost is real because an identity integration is never finished. Every IdP formats its claims a little differently. Group mapping, session lifetime, redirect URLs and clock skew each produce login bugs, and a login bug locks out every user at once, so those tickets are urgent. Then come the follow-on requests: nested groups, role mapping, automatic provisioning with SCIM (system for cross-domain identity management), and audit logs that a compliance team will read.
The revenue side is arithmetic rather than malice. A company that cannot connect the app to its own IdP will not deploy it at all, so SSO marks a clean line between the user who pays and the user who does not. An open core project has to put that line somewhere. SSO sits on it more neatly than almost any other feature, which is why so many projects pick it.
One correction to the usual complaint: check the changelog before you assume a feature was taken away, because a removal shows up in release notes. Across the projects I checked for this post, the paid SSO features were built for the paid tier from the start. I did not find a case of working free SSO being withdrawn. Grafana is typical here. Its SAML page carries a one line note, "Available in Grafana Enterprise and Grafana Cloud", while generic OAuth against your own issuer works in the open source build.
What the SSO tax actually costs you
The money is the smaller half. Paid tiers are sold per user, so the bill grows with your team while the hosting and the upgrades stay your job.
The larger cost is manual identity work, and it lands in four places.
- One password store per app, so one reused password is a hole in every app that shares it.
- Offboarding from memory. You have to remember every service a person touched, and the one you forget is the one that matters.
- MFA configured app by app, where the app supports it at all.
- Shared logins, which is what actually happens in small teams under this pressure.
That last point deserves a sentence of its own. When a team shares one administrator account in a document manager, the audit trail records one name for everything, so you cannot tell who deleted the invoice. Per-user permissions stop working too, because there is only one user. That is the real damage from the SSO tax: it pushes small teams toward a single shared account, which is worse than any of the alternatives.
The checklist to run before you adopt anything
Run this before docker compose up, not after the app holds 400 documents.
- Open the authentication page in the docs and read the tier note at the top. Paid features carry a badge or a one line availability sentence.
- Confirm the app speaks OIDC or SAML to your own issuer, rather than to a fixed list of public providers.
- Check role and group mapping. Creating the user is half the job, and assigning permissions by hand in ten apps is the half that hurts.
- Check whether the app accepts an authenticated username in a header from a trusted proxy, and whether you can pin which proxy it trusts.
- Read the licence history in git, and check whether contributors sign a CLA (contributor licence agreement).
- Check offboarding. Find out what happens to API (application programming interface) tokens and live sessions when the IdP account is disabled.
Item 2 is where most of the disappointment happens. A "Sign in with Google" button is not OIDC with your identity provider: it is a fixed integration with one vendor. Real support asks you for an issuer URL, and everything else comes from discovery. You can confirm your provider's side of that in one command.
curl -s https://id.example.com/.well-known/openid-configuration \
| jq '.issuer, .authorization_endpoint, .token_endpoint'Three URLs come back from a healthy provider. An empty result or a 404 usually means the discovery path is wrong, and that path is provider specific: Keycloak publishes it under /realms/<realm>/.well-known/openid-configuration. If the app has no field for an issuer URL at all, it cannot talk to your IdP, whatever the feature list says.
Item 6 catches people weeks after someone leaves. Disabling the account in the IdP stops new logins. It does not revoke an API token the app issued earlier, because the app validates that token itself and never asks the IdP about it. Offboarding therefore has two steps: disable the account in the IdP, then delete the user or its tokens inside each app.
What the tier badges actually say
These are checked against each project's own documentation in August 2026. Start with the paid side.
Grafana publishes SAML as "Available in Grafana Enterprise and Grafana Cloud", along with team sync and SCIM provisioning. Generic OAuth, GitHub OAuth, LDAP (lightweight directory access protocol) and the auth proxy are all in the open source build, so a small self-hoster can still log in through their own provider. The paid line falls at SAML rather than at single sign-on as a whole, which is the nuance the phrase "SSO tax" tends to flatten.
Metabase is blunter. Its documentation says, "SAML authentication is only available on Pro and Enterprise plans (both self-hosted and on Metabase Cloud)." The open source edition keeps password login and LDAP.
Passbolt marks its SSO documentation Pro and Cloud, so the community edition does not have it. The documented providers include Keycloak and Entra ID.
Now the other side, because this pattern is far from universal.
- GitLab Self-Managed carries "Tier: Free, Premium, Ultimate" on its SAML page, so SAML against your own GitLab costs nothing.
- Paperless-ngx configures OIDC through django-allauth with
PAPERLESS_SOCIALACCOUNT_PROVIDERS, hides the local login form withPAPERLESS_DISABLE_REGULAR_LOGIN, and maps claims onto groups withPAPERLESS_SOCIAL_ACCOUNT_SYNC_GROUPS. - Planka takes
OIDC_ISSUER,OIDC_CLIENT_IDandOIDC_CLIENT_SECRET, defaults its scopes toopenid profile email, and promotes administrators from a role claim withOIDC_ADMIN_ROLES. - BookStack switches over with
AUTH_METHOD=oidc, then maps provider groups onto its own roles withOIDC_USER_TO_GROUPS=trueandOIDC_GROUPS_CLAIM. - Vaultwarden shipped "support for SSO with OpenID Connect" in 1.35.0 on 27 December 2025, from a pull request by a contributor who had carried the feature in a fork.
- listmonk has had OIDC login alongside its user roles since v4.0.0.
Use that when you are choosing rather than after you have committed. A Planka kanban board and the other self-hosted Trello alternatives do not all treat identity the same way, and neither do BookStack, Wiki.js and Outline. Free OIDC is a feature you can weigh like storage limits or mobile clients. If you are still drawing up the list, what to self-host in 2026 is a reasonable starting point, and both the Paperless-ngx document manager and Vaultwarden give you free OIDC today.
Why a reverse proxy in front of the app is not single sign-on
The common workaround is forward auth. Your reverse proxy holds each request, asks an authentication service whether this browser is signed in, and only then passes the request to the app. authentik calls this a proxy provider, with a forward auth mode for a single application and another for a whole domain. Authelia and oauth2-proxy do the same job.
A Caddy site block looks like this, following authentik's own example.
app.example.com {
forward_auth http://authentik-outpost:9000 {
uri /outpost.goauthentik.io/auth/caddy
copy_headers X-Authentik-Username X-Authentik-Email X-Authentik-Groups
trusted_proxies private_ranges
}
reverse_proxy app:8000
}Capitalisation of those header names matters in Caddy, because a mismatched name arrives empty. On every request it approves, the outpost sets X-authentik-username, X-authentik-email, X-authentik-groups and a few more.
Here is what that buys you. Nobody reaches the app without passing your identity provider first, so an unpatched login form is no longer exposed to the internet, and MFA applies to everything behind the proxy at once.
Here is what it does not buy you: identity inside the app. The app still has its own accounts and its own idea of who is signed in. If everyone passes the proxy and lands on one shared administrator account, you have a strong front door and one anonymous session behind it. The audit log still shows a single name. Permissions still cannot differ between people. Calling that arrangement SSO is a security mistake, because the offboarding story is only half true: removing the person from your IdP closes the front door, while an API token they created inside the app keeps working for anyone who can reach the app directly.
Making header authentication safe
Some apps do accept a username from the proxy, which gives you per-user identity without paid SSO. The setting has a different name in each project.
Grafana calls it the auth proxy and ships it disabled. The header name defaults to X-WEBAUTH-USER, and you can point it at whatever header your proxy sets.
[auth.proxy]
enabled = true
header_name = X-authentik-username
header_property = username
auto_sign_up = true
whitelist = 10.0.0.5whitelist is the line people skip. Grafana's documentation is explicit that it exists to stop users spoofing the header, so it should hold the address of your proxy and nothing else. Gitea has the same feature under different names, and it ships with a safer default.
[security]
ENABLE_REVERSE_PROXY_AUTHENTICATION = true
REVERSE_PROXY_AUTHENTICATION_USER = X-WEBAUTH-USER
REVERSE_PROXY_TRUSTED_PROXIES = 10.0.0.5/32
REVERSE_PROXY_LIMIT = 1REVERSE_PROXY_TRUSTED_PROXIES defaults to 127.0.0.0/8,::1/128, and REVERSE_PROXY_LIMIT is how many proxies Gitea will trust in the chain. Setting that limit to zero turns the header handling off entirely.
Paperless-ngx offers PAPERLESS_ENABLE_HTTP_REMOTE_USER with PAPERLESS_HTTP_REMOTE_USER_HEADER_NAME, and its documentation states the warning that belongs on all of these settings:
This will allow authentication by simply adding a Remote-User: <username> header to a request. Use with care!
Two rules keep header authentication safe, and both are about reachability. First, the app must be unreachable except through the proxy, because anyone who can open a socket to it can send that header and become any user. In Docker, ports: ["8000:8000"] publishes on every interface, so bind it to the loopback address with ports: ["127.0.0.1:8000:8000"], or drop the published port and put the proxy on the same Docker network. Second, the proxy must delete any copy of the header that arrives from the client, so the only value the app ever sees is the one your proxy sets after authentication.
Check both. Run the first command from a machine outside your VPS and the second on the server itself.
curl -si -H "Remote-User: admin" http://203.0.113.10:8000/ | head -n 1
ss -ltnp | grep 8000The curl should fail to connect, and ss should print 127.0.0.1:8000 rather than 0.0.0.0:8000. A first line of HTTP/1.1 302 Found means the app is answering the public internet directly, so anyone can sign in as any user by naming them in a header.
When there is no free SSO, decide instead of complaining
Four options, in the order I would try them.
- Pick the app that includes OIDC. Where two projects do the same job and one talks to your identity provider for free, that is a real difference in running cost.
- Use forward auth honestly. For an admin tool with one account and one operator, a proxy in front is enough, and per-user identity inside the app buys nothing.
- Pay. If the app is central to your work and the per-seat price fits your team size, the money keeps the project maintained, and the alternative is paid in your evenings.
- Ask upstream, after searching the issue tracker. Vaultwarden's OIDC support arrived through a contributor's fork and a long-running pull request, so a feature request with a working implementation behind it does sometimes land in the free edition.
None of this works without an identity provider of your own, which is the piece to build first. Running authentik on a VPS gives you an OIDC and SAML provider plus the forward auth outpost used above, and the comparison of Keycloak, authentik and Zitadel covers the trade-offs if you would rather not commit yet.
Licence history, and why it is on the checklist
The last checklist item is about the future, because today's tier layout is only a snapshot. Two well documented cases show how fast the ground moves, in both directions. HashiCorp adopted the Business Source License 1.1 for all future releases on 10 August 2023, while earlier releases stayed under MPL 2.0 (Mozilla Public License). Redis moved to the SSPL (server side public license) in March 2024, then announced on 1 May 2025 that Redis 8 also ships under the AGPLv3 (GNU Affero General Public License).
Read both as evidence about mechanism rather than motive. The licence you read today applies to the version you install today, and a project that holds all of its copyright can change the terms of the next release on its own. That is why the CLA question is on the checklist, since broad copyright assignment is what makes a unilateral relicence possible.
Check a project's history yourself before you build on it.
git clone --filter=blob:none https://github.com/paperless-ngx/paperless-ngx.git
cd paperless-ngx
git log --follow --oneline -- LICENSEA short list of commits, mostly from the first import, is a good sign. Several rewrites of the licence file mean you should read each commit message before you plan around the current terms.
FAQ
What is the SSO tax?
The SSO tax is the practice of charging for single sign-on as a premium feature while the rest of the product is free or cheap. In self-hosted software it looks like an open source application you can run without a licence key, where OIDC or SAML login sits in a paid tier. The name comes from the SSO Wall of Shame at sso.tax, which tracks vendors charging a large premium for it. The consequence for a self-hoster is that every app keeps its own user database, so accounts are created and removed by hand.
Is a reverse proxy with forward auth the same as SSO?
No. Forward auth protects the front door: your proxy checks with the identity provider before any request reaches the app. The app behind it still uses its own accounts, so if everyone lands on one shared login you get a single anonymous session and an audit log with one name in it. It becomes real per-user identity only when the app reads the username from a header, which Grafana's auth proxy, Gitea's reverse proxy authentication and Paperless-ngx's PAPERLESS_ENABLE_HTTP_REMOTE_USER can all do. Those settings are safe only while the app cannot be reached except through the proxy, because the header is a plain string that any client can send.
Which self-hosted apps include OIDC in the free edition?
Checked against project documentation in August 2026: Paperless-ngx, Planka, BookStack, Gitea, listmonk and Vaultwarden all support OIDC in their free builds, and GitLab Self-Managed lists SAML under Tier: Free. Grafana's open source build handles generic OAuth against your own issuer, while SAML there is an Enterprise feature. Confirm on the project's own authentication page before you install, because these lists change with releases.
Should I pay for the tier that unlocks single sign-on?
Decide it with two numbers: how many people need accounts, and how many apps you would otherwise maintain by hand. For one or two administrators, forward auth in front of a local account is enough and the paid tier buys little. For a team where people join and leave, one account missed during offboarding costs more than the licence, and the payment funds the maintenance you are relying on. If the price does not fit, the practical move is choosing an app that includes OIDC rather than working around one that does not.