Three years ago, I got a call from a fintech startup CTO I had helped architect their platform. They were not calling to celebrate a successful launch. They were calling because Auth0 had just sent them a renewal quote that was four times what they had been paying. They had crossed some usage tier threshold, their users had grown, their token issuance volume had spiked with new API products, and suddenly identity was going to consume a significant chunk of their infrastructure budget. “We built the whole thing around Auth0,” they told me. “What do we do?”
I had seen this story before. Auth0, Okta, AWS Cognito, Azure AD B2C: managed identity providers are seductive at the start. You add a few lines of SDK code, your authentication works, and you ship features instead of operating infrastructure. But identity is load-bearing. When the pricing changes, when the managed provider has an outage, or when your compliance team informs you that all authentication data must stay within your own infrastructure, you discover that identity is not something you can casually swap out.
That fintech team ended up migrating to Keycloak. It took about six weeks of careful work, and they landed on a self-hosted identity stack that now costs them roughly what they were paying Auth0 in 2021, handles ten times the token volume, and runs entirely inside their own AWS VPC. This article is what I wish I had handed them at the start of that migration.
What Keycloak Actually Is
Keycloak is an open-source identity and access management platform maintained by Red Hat and backed by the CNCF ecosystem. At its core, it is an OAuth2 authorization server and OpenID Connect provider that can also speak SAML 2.0 for enterprise integrations. It handles user registration, authentication, session management, token issuance, and federation to external identity sources like LDAP, Active Directory, and social providers.
The concepts you need to understand are realms, clients, and flows.
A realm is Keycloak’s top-level organizational unit. Think of it as a tenant. You might have one realm for your customer-facing application, another for internal employee tools, and a third for machine-to-machine service authentication. Each realm has its own user database, its own client registrations, its own token settings, and its own authentication flows. Realms are completely isolated from each other by default, which makes Keycloak naturally suited for multi-tenant deployments.
A client in Keycloak represents an application that wants to authenticate users or obtain tokens. Your frontend SPA is a client. Your backend API that validates JWTs is a client. Your ArgoCD installation that authenticates operators is a client. Each client gets its own configuration: redirect URIs, token lifetimes, allowed scopes, whether it uses the public client flow or confidential client with a secret.
Authentication flows are where Keycloak earns its reputation for flexibility and complexity simultaneously. A flow is a sequence of authentication steps: enter username, check if password-based auth is required, prompt for TOTP if MFA is enabled, check for suspicious IP, issue tokens. You can build custom flows, add custom authenticators written as Java extensions, and chain them together in ways that can model nearly any enterprise authentication requirement. The flexibility is real. So is the footgun potential.
For the deep background on the protocol layer, see our federated identity guide and the SSO architecture explainer – this article focuses on the operational side of running Keycloak in production.

The Case for Self-Hosted Identity
I am not going to pretend managed identity providers are bad products. Auth0 is well-engineered. Okta has enterprise features that would take years to replicate. AWS Cognito integrates cleanly with the rest of the AWS ecosystem. For many teams, especially early-stage ones, a managed provider is the right call.
But there are situations where running your own makes sense, and I see them more often than I used to.
Cost at scale. Managed providers typically charge per Monthly Active User (MAU) or per token. At small user counts this is negligible. At hundreds of thousands of users with aggressive token issuance from mobile clients and API partners, the bill becomes a real infrastructure budget line item. Keycloak’s cost model is compute, not users.
Data residency and compliance. Some regulated industries require that authentication data, session tokens, and user credentials never leave a specific geographic boundary or a specific infrastructure environment. GDPR, HIPAA in certain interpretations, financial services regulations in some jurisdictions: all of these can push you toward self-hosted. A managed provider can offer data residency guarantees, but the contract and audit implications are often more complex than just running it yourself inside your own infrastructure.
Deep customization. When I have needed custom authentication flows – step-up authentication based on resource sensitivity, custom login pages that match a complex brand guideline down to the pixel, authentication logic that calls internal APIs during the flow – Keycloak’s extension model (Service Provider Interfaces, or SPIs) handles it. Managed providers offer customization up to a point, and then you hit walls.
Avoiding vendor lock-in. The authentication protocols are standardized: OIDC, OAuth2, SAML. But the specific SDK integration patterns, the custom claims structure, the admin APIs you use to provision users programmatically – these vary between providers. Moving off Auth0 is painful not because of the protocol, but because of all the provider-specific behaviors you have built around it. Keycloak is open source, so you own the full stack.
Deploying Keycloak on Kubernetes
The recommended production deployment path is the Keycloak Operator. The operator manages the Keycloak custom resource, handles rolling updates, configures the Infinispan distributed cache for session clustering, and integrates with cert-manager for TLS. Do not try to deploy Keycloak via raw Deployment manifests in 2026. The operator exists precisely because Keycloak has cluster state that needs careful coordination on updates.
Install the operator from the official CRD bundle:
kubectl apply -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/refs/heads/main/kubernetes/keycloaks.k8s.keycloak.org-v1.yml
kubectl apply -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/refs/heads/main/kubernetes/keycloakrealmimports.k8s.keycloak.org-v1.yml
kubectl apply -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/refs/heads/main/kubernetes/kubernetes.yml
A minimal production Keycloak custom resource looks like this:
apiVersion: k8s.keycloak.org/v2alpha1
kind: Keycloak
metadata:
name: keycloak
namespace: identity
spec:
instances: 3
db:
vendor: postgres
host: postgres-cluster.identity.svc.cluster.local
database: keycloak
usernameSecret:
name: keycloak-db-credentials
key: username
passwordSecret:
name: keycloak-db-credentials
key: password
http:
tlsSecret: keycloak-tls
hostname:
hostname: auth.example.com
cache:
stack: kubernetes
ingress:
enabled: false
A few things worth highlighting here. The instances: 3 field gives you three Keycloak pods that cluster together using Infinispan for distributed session state. If you run a single instance, you have a single point of failure for authentication across your entire platform. I have seen this cause embarrassing outages. The cache.stack: kubernetes setting tells Keycloak to use Kubernetes service discovery for Infinispan cluster formation rather than multicast UDP (which does not work in most Kubernetes CNI configurations). For more on Kubernetes networking fundamentals that affect this, see the CNI and network policies guide.
Database requirements. Keycloak stores everything in a relational database: users, sessions, realms, clients, events. PostgreSQL is the recommended choice. The database needs to be highly available – if your Keycloak database goes down, nobody can authenticate. This means either a managed database service (RDS Multi-AZ, Cloud SQL with HA) or a well-operated PostgreSQL operator like CloudNativePG inside your cluster. Do not share this database with application workloads; give identity its own database instance.
TLS is non-negotiable. Keycloak needs TLS termination, and it can be configured to terminate TLS at the Keycloak pod itself (recommended for mTLS environments) or at an ingress/load balancer. I prefer terminating at the ingress and passing HTTP internally when the cluster network itself is trusted, but for environments with strict security requirements, end-to-end TLS with cert-manager and a wildcard or SAN certificate is cleaner. See the TLS certificate management guide for the cert-manager setup that integrates well here.
Configuring OIDC for Your Applications
Once Keycloak is running, you need to register your applications as clients and configure the authentication flow. For a typical web application:
- Create a realm for your application tier (do not use the
masterrealm for application authentication – reserve master for administrative access to Keycloak itself). - Create a client with client type “OpenID Connect,” set the redirect URIs to your application’s callback endpoints, and choose confidential access if the application has a backend that can keep a secret.
- Configure token lifetimes. Access tokens should be short (5 to 15 minutes). Refresh tokens control how long a user session persists. Use offline tokens sparingly – they are long-lived and need careful revocation handling.
- Map claims into your tokens. By default, Keycloak includes basic OIDC claims. You can add custom claims via protocol mappers: user attributes, group memberships, realm roles, client roles. This is how you pass authorization context to your application without the application querying Keycloak on every request.
The discovery document at https://auth.example.com/realms/your-realm/.well-known/openid-configuration gives your applications everything they need: the JWKS endpoint for token validation, the authorization endpoint, the token endpoint, the userinfo endpoint. Most OIDC-aware frameworks (Spring Boot, FastAPI, Next.js with next-auth, Nginx auth_request) can be pointed at this discovery URL and auto-configure.
One thing I always configure explicitly: token validation in the application must be done locally against the JWKS public keys, not by calling Keycloak’s introspection endpoint on every request. I have seen teams implement the introspection approach, which adds a network hop to Keycloak on every authenticated API call. At scale this becomes a bottleneck and a single point of failure. JWT validation is a CPU operation. Do it locally.
Kubernetes API Server OIDC Integration
One of the underappreciated capabilities of Keycloak in a cloud-native environment is using it to authenticate kubectl users against the Kubernetes API server via OIDC. Instead of distributing static kubeconfig files with long-lived certificates, you get short-lived tokens issued by Keycloak, bound to a user’s identity, with all the MFA and session policies your realm enforces.
The API server flags you need:
--oidc-issuer-url=https://auth.example.com/realms/kubernetes
--oidc-client-id=kubectl
--oidc-username-claim=preferred_username
--oidc-groups-claim=groups
--oidc-username-prefix=oidc:
--oidc-groups-prefix=oidc:
Create a Keycloak client called kubectl in a dedicated realm. Add a groups mapper that puts the user’s Keycloak group memberships into the groups claim in the ID token. Then create Kubernetes ClusterRoleBindings that bind oidc:platform-engineers (or whatever group name you use) to the appropriate RBAC roles.
The user workflow: run kubectl oidc-login get-token (using the kubelogin plugin), a browser window opens to Keycloak, they authenticate with MFA, and they get a short-lived token that kubectl uses for API requests. When the token expires, they authenticate again. This model eliminates certificate sprawl and ties Kubernetes access directly to your identity lifecycle management – when someone leaves the organization and their Keycloak account is disabled, their cluster access ends immediately.
This pairs well with the Kubernetes RBAC setup covered elsewhere on this site.

Identity Federation: LDAP, Active Directory, and Social Providers
Most enterprises have an existing identity source. Keycloak’s user federation capability lets it delegate authentication to external directories while still serving as the OIDC/SAML facade your applications talk to.
For Active Directory or OpenLDAP, configure a User Federation provider in the realm settings. Keycloak will authenticate against the directory using bind credentials, import user attributes on first login, and optionally cache user data locally. The import mode is important to get right: “Lazy” import means user data is pulled from LDAP on demand; “Full” sync can be scheduled. For large directories with hundreds of thousands of entries, be careful with sync intervals and filter your LDAP query to import only the groups your applications actually need.
For social providers (Google, GitHub, Microsoft), Keycloak handles the OAuth2/OIDC handshake with the external provider and maps the resulting claims to a local Keycloak user. You can configure whether Keycloak creates a new local user on first social login or links to an existing account by email.
The identity brokering flow – the sequence of steps Keycloak takes when a user authenticates via an external provider – is configurable per broker. This lets you enforce additional factors (first-login requires profile completion, every login from a new IP requires step-up MFA) even when the upstream provider does not support them.
For zero-trust architecture, Keycloak serves as the policy enforcement point for “verify identity continuously” – every token is time-limited, every session can be revoked, and device posture checks can be embedded in custom authenticators.
Production Gotchas I Learned the Hard Way
Session store sizing. Infinispan stores active sessions in memory distributed across the Keycloak pod cluster. The default cache sizes are conservative. If your application has aggressive token refresh cycles – single-page apps that refresh every five minutes, mobile clients that maintain always-on sessions – you will exhaust the default heap faster than you expect. Monitor Keycloak’s JVM heap usage from day one and tune the --cache-stack-kubernetes-initial-cluster-size and Infinispan cache configs accordingly.
Database connection pooling. Keycloak uses Agroal for connection pooling. The default pool size of 100 connections sounds generous until you have 10 Keycloak pods under authentication load hitting your PostgreSQL cluster simultaneously. Do the math before your peak traffic event: N pods times the pool size must not exceed what your database can handle. You will probably want a connection pooler like PgBouncer between Keycloak and PostgreSQL. See the connection pooling guide for the setup that works here.
Realm export and configuration as code. Clicking through the Keycloak admin console to configure realms is fine for initial setup and exploration. For production, you need realm configuration in code. Keycloak supports realm import via JSON exports and, more importantly, via the Keycloak Operator’s KeycloakRealmImport CRD. Combined with a GitOps approach, this means realm configuration is version-controlled, reviewable, and automatically applied to new environments. I use a separate Git repository for identity configuration that follows the same PR-and-review process as application code. When someone wants to add a new OIDC client or modify an authentication flow, it goes through code review, not the admin console.
Admin credentials. The Keycloak admin user (for the master realm) is extremely sensitive. Do not store admin credentials in plain-text Kubernetes Secrets. Integrate with secret management using External Secrets Operator pulling from HashiCorp Vault or AWS Secrets Manager. Rotate admin credentials on a schedule. Restrict the admin console to an internal IP range or require a VPN connection. I have seen Keycloak admin consoles exposed to the internet – do not do this.
Upgrade strategy. Keycloak releases are frequent and sometimes include breaking changes to the database schema or the Keycloak SPI extension API. Before any upgrade: back up the database, test the upgrade in a non-production environment, check the migration notes for your extension code. The Keycloak Operator handles rolling upgrades gracefully, but only if your database schema migration succeeds. I always pin to a specific Keycloak version in the operator CRD and treat upgrades as a deliberate operational event.
Monitoring. Keycloak exposes Prometheus metrics at /metrics. Instrument for: login success and failure rates per realm, token refresh rates, Infinispan cache hit rates, database connection pool utilization, and JVM heap. Alert on elevated login failure rates (could indicate a credential stuffing attack) and on high cache miss rates (session clustering is broken). MFA enrollment rates are worth tracking too – if your MFA enforcement is working, nearly all active users should have an MFA factor registered. See our MFA guide for the broader context on MFA policies.
When NOT to Use Keycloak
I have advocated for Keycloak here, but I want to be honest about when it is the wrong choice.
Small teams with simple requirements. If you have ten internal users, a handful of applications, and no compliance requirements that force self-hosting, Auth0’s free tier or Cognito’s low-volume pricing is far cheaper in engineering time than deploying and operating Keycloak. The break-even point where Keycloak saves money and complexity is higher than most people assume.
Consumer-scale B2C with exotic social login requirements. Auth0 and Okta Customer Identity have excellent out-of-the-box support for dozens of social providers, progressive profiling, and consumer-focused authentication UX patterns. Replicating this in Keycloak is possible but time-consuming. If your product is a consumer application where authentication UX is a core product feature, the investment in custom Keycloak flows may not be worth it.
No Kubernetes expertise on the team. Keycloak on Kubernetes is not hard if your team already operates Kubernetes workloads. But if your team has never dealt with stateful clustered applications, PersistentVolumes, or Kubernetes networking, the operational overhead of getting Keycloak right is real. Self-hosting identity is a commitment to operating it correctly – a misconfigured or poorly monitored Keycloak installation is worse than a managed provider, not better.
For context on what running stateful applications on Kubernetes actually involves, see databases on Kubernetes – the lessons there apply directly to Keycloak.
Security Hardening Checklist
Running your own identity provider means the security of your authentication infrastructure is your responsibility. A few things I treat as non-negotiable:
- Enable brute-force detection in every realm. Keycloak can lock out accounts after N failed login attempts and throttle login attempts per IP.
- Require MFA for all administrative users and for any user accessing sensitive applications.
- Set aggressive token lifetimes for sensitive clients. A five-minute access token with a two-hour refresh token is the right default for most enterprise applications.
- Enable event logging to a SIEM. Keycloak emits structured events for every login, logout, token refresh, and admin action. These belong in your security data lake. See the cloud-native SIEM guide for where to route them.
- Use separate realms for internal users and external customers. Do not mix them.
- Restrict admin console access to internal networks or require admin authentication itself to go through an MFA-enforced flow.
- Regularly review registered clients and remove orphaned ones. Every registered client is a potential attack surface.

The Bigger Picture
In twenty years of building cloud infrastructure, the pattern I have seen repeatedly is that shared infrastructure primitives – storage, networking, compute, identity – tend to migrate from “buy from a vendor” to “run as a managed service” to “operate yourself in a standardized way” as the tooling matures and the operational knowledge diffuses through the industry.
Identity is following that same arc. Keycloak is not a new project; it has been running in production at significant scale since before many of the engineers I work with started their careers. The Kubernetes-native deployment story, the Operator, the GitOps-friendly realm configuration, the Infinispan clustering – all of this is stable and well-understood.
The teams I have seen succeed with self-hosted Keycloak have a few things in common: they treat identity infrastructure with the same operational rigor as their databases, they invest in realm configuration as code from day one, and they monitor Keycloak as a first-class production system rather than an afterthought. The teams I have seen struggle treated it as a set-and-forget configuration that they could hand off to a junior engineer after the initial deploy.
Identity is not an area where cutting operational corners pays off. The failure mode – authentication down means no one can log in to anything – is too visible and too painful. But if you are willing to operate it with the same care you give your databases and your message brokers, Keycloak gives you an identity platform that is flexible, portable, and effectively free to run regardless of your user count.
The fintech team from the beginning of this article has not regretted the migration. Their Auth0 bill is gone. Their compliance auditors can inspect the full authentication infrastructure. And the last time I checked in with them, they had extended Keycloak with a custom authenticator that implements step-up authentication for high-value transactions, which Auth0 would have required an enterprise plan upgrade to support. Total implementation time: one sprint.
That is the kind of optionality that self-hosted, open-source infrastructure gives you. Just make sure you’re ready to operate it before you commit.
Get Cloud Architecture Insights
Practical deep dives on infrastructure, security, and scaling. No spam, no fluff.
By subscribing, you agree to receive emails. Unsubscribe anytime.
