Keycloak Cache Configuration: Infinispan and KC_CACHE_STACK

Guilliano MolaireGuilliano MolaireUpdated 14 min read

How does Keycloak caching work?

Keycloak caches realms, clients, users and sessions in memory with Infinispan, an embedded Java data grid, so most logins never wait on the database. In production mode (kc.sh start), distributed caching is on by default, and since Keycloak 26.1 the nodes find each other through the shared database using the jdbc-ping transport stack.

That short answer covers most clusters, because the defaults in Keycloak 26.x are much better than they were a few years ago. A lot of the caching advice still circulating online was written for Keycloak 25 or earlier, when you had to pick a discovery protocol, edit cache-ispn.xml to keep sessions safe and wire up encryption yourself. This guide explains which caches Keycloak uses, what the KC_CACHE and KC_CACHE_STACK settings do today, how to size the caches, how the traffic between nodes is secured, and how to check that a cluster has formed and is caching well. Every option below was checked against the Keycloak distributed caching guide and the 26.x upgrade notes.

The caches Keycloak uses and what they hold

Keycloak’s caching layer is built on Infinispan, which runs inside each Keycloak process. You do not install or operate a separate cache server for a standard cluster. Keycloak uses three kinds of cache:

  • A local cache lives on one node only. Each node keeps its own copy of data it read from the database.
  • A replicated cache holds the same entries on every node.
  • A distributed cache spreads entries across the nodes, so any node can read an entry even if it lives on another node.

This table lists the caches Keycloak creates, based on the official caching guide for the current 26.x release:

Cache Type What it holds
realms Local Realm data, including clients, roles and groups
users Local User data, including granted roles and group memberships
authorization Local Authorization Services resources, permissions and policies
keys Local Public keys fetched from external clients and identity providers
crl Local Certificate revocation lists for the X.509 authenticator
work Replicated Short-lived invalidation messages sent between nodes
authenticationSessions Distributed State for logins that are still in progress
sessions, clientSessions Distributed Cached copies of user and client sessions
offlineSessions, offlineClientSessions Distributed Cached copies of offline sessions (backed by offline tokens)
loginFailures Distributed Failed login counters for brute force detection; since 26.8 these live in the database by default, and this cache is only used with the deprecated login-failures:v1 feature
actionTokens Distributed Metadata for action tokens, such as the links in password reset emails

Local caches and how invalidation works

The realms, users and authorization caches hold up to 10,000 entries each by default, and the keys cache holds up to 1,000 entries that expire after one hour, so Keycloak periodically re-downloads external keys. Because these caches are local, each node needs a way to learn that its copy is stale. When one node changes data in the shared database, it sends a message through the replicated work cache, and the other nodes drop that entry and reload it from the database the next time it is needed. The work cache entries are very short-lived, so you should not see it grow over time.

Session caches in Keycloak 26

This is the part of Keycloak caching that changed most. Since Keycloak 26.0 the persistent-user-sessions feature is on by default, which means user and client sessions are stored in the database and the session caches only hold a working copy. The 26.0 upgrade notes explain that the session caches now default to 10,000 entries per node with one owner per entry (an owner is a node that holds a copy of the entry), and Keycloak 26.3 started enforcing safe owner and size settings for them automatically.

In practice, that means the old advice to set owners="2" on the sessions and clientSessions caches, or to remove their <memory max-count> limit, no longer applies. Losing a node does not log anyone out, because the session is still in the database and another node reloads it on demand. The older in-memory mode, called volatile user sessions, is turned on with --features-disabled=persistent-user-sessions and is deprecated as of Keycloak 26.8, so plan to remove that flag if you still use it. Our post on Keycloak session management and timeouts covers how session lifetimes interact with these caches.

How to enable distributed caching in Keycloak

Distributed caching needs no special switch in production, but it helps to know what each start mode does:

  1. Start Keycloak in production mode. Running bin/kc.sh start enables distributed Infinispan caching, which is the same as --cache=ispn (or KC_CACHE=ispn as an environment variable).
  2. Point every node at the same database. The default discovery stack uses that database to find the other nodes, so nodes that share a database and a cluster name form one cluster.
  3. Open the cluster ports between nodes. Data travels over TCP port 7800, and failure detection uses port 57800. The network section below has the details.
  4. Check the logs for a cluster view. Each node logs ISPN000094 with the list of members when a node joins or leaves.

You can make the cache mode explicit with this command:

bin/kc.sh start --cache=ispn

Development mode is different. bin/kc.sh start-dev sets --cache=local, which uses local caches only and disables distributed caching completely. That is fine on a laptop, but never run a multi-node setup in development mode, because the nodes will not share sessions or invalidations.

What KC_CACHE_STACK does and which value to use

KC_CACHE_STACK (the --cache-stack option) picks the JGroups transport stack. JGroups is the networking library Infinispan uses, and a stack decides two things: how nodes discover each other and which protocol they use to exchange cache data. When you leave it unset, Keycloak uses jdbc-ping, which discovers nodes through a table in the Keycloak database and sends data over TCP.

Stack Transport Discovery Status in 26.x
jdbc-ping TCP Keycloak database (JGroups JDBC_PING2) Default since 26.1, recommended
jdbc-ping-udp UDP Keycloak database Deprecated since 26.1
kubernetes TCP DNS lookup of a Kubernetes headless service (DNS_PING) Deprecated since 26.3
tcp TCP IP multicast (MPING) Deprecated since 26.1
udp UDP IP multicast (PING) Deprecated since 26.1, was the default before 26.1
azure, ec2, google TCP Cloud provider specific Deprecated since 26.1

The Keycloak 26.1 release notes explain why the default changed: the old UDP multicast default needed multicast networking, which most cloud networks do not provide. Using the database for discovery removes that requirement, and in Keycloak 26.3 the kubernetes stack was also deprecated and the Keycloak Operator switched to jdbc-ping by default. Keycloak’s option code marks every value except jdbc-ping as deprecated with the hint “Use ‘jdbc-ping’ instead by leaving it unset”, so on a current version you should simply leave KC_CACHE_STACK unset.

If you have an older Kubernetes deployment that still sets KC_CACHE_STACK=kubernetes, it keeps working for now, and it looks like this:

# Deprecated since Keycloak 26.3: prefer leaving KC_CACHE_STACK unset (jdbc-ping)
bin/kc.sh start --cache=ispn --cache-stack=kubernetes
export JAVA_OPTS_APPEND="-Djgroups.dns.query=keycloak-discovery.keycloak.svc.cluster.local"

The jgroups.dns.query property must name the headless service that resolves to the Keycloak pods. A headless service is a Kubernetes service with no cluster IP, so a DNS lookup on it returns the address of each pod. To move to the default, remove both settings, make sure every pod uses the same database, and roll the pods. Our Keycloak on Kubernetes production guide walks through the rest of a Kubernetes setup.

Requirements before setting up a Keycloak cluster

Memory and CPU sizing for caching

Caching is mostly a memory question, and Keycloak’s own load tests give a starting point. The Keycloak sizing guide recommends:

  • About 1,250 MB of RAM per pod as a base, which includes the realm caches and 10,000 cached sessions.
  • Heap is 70% of the container memory limit, and Keycloak also uses roughly 300 MB of non-heap memory. To turn a memory request into a limit, subtract the non-heap memory and divide the result by 0.7.
  • 1 vCPU for every 15 password logins per second, 1 vCPU per 120 client credentials grants per second and 1 vCPU per 120 refresh token requests per second, across the cluster.
  • 150% extra CPU head-room for load spikes, node startup and failover.

CPU is driven by request rate, not by how many users are registered, because most of the CPU time goes into password hashing and TLS handshakes. Our guide on how much RAM and CPU Keycloak needs in production works through these numbers with examples.

Network ports and bind address

Nodes in a jdbc-ping cluster need to reach each other on two TCP ports:

Port Option or property Purpose
7800 cache-embedded-network-bind-port (jgroups.bind.port) Unicast cache data between nodes
57800 jgroups.fd.port-offset (default offset 50000 from 7800) Failure detection, so a node notices quickly when another one dies

The cluster port must be bound to an interface every other node can reach. By default Keycloak picks a site-local address, such as one in 10.0.0.0/8 or 192.168.0.0/16, and you can override it with cache-embedded-network-bind-address, which accepts an IP or special values such as match-interface:eth.*. If nodes sit on different networks behind port forwarding, set cache-embedded-network-external-address and cache-embedded-network-external-port so each node advertises the address the others should use. These options are the supported way to do what the jgroups.external_addr and jgroups.external_port system properties did in older guides.

A shared database and a cluster name

Because jdbc-ping uses the database for discovery, every node in a cluster must use the same database. The flip side is that separate environments, such as staging and production, should each have their own database. Two deployments that share a database without different cluster names merge into one cluster, and giving them different names is not a fix on its own: without the stateless feature, one deployment never invalidates the other’s cached realm data, which leads to stale caches. Since Keycloak 26.8, nodes using jdbc-ping that see another cluster name in the same database log an error and report themselves unhealthy unless stateless is enabled. If two deployments really must share a database, the 26.8 upgrade notes recommend enabling --features=stateless and giving each deployment a unique --cache-embedded-cluster-name. That option, new as a first-class setting in 26.8, replaces the older spi-cache-embedded--default--cluster-name SPI option, and the default name is ISPN. Setting a stable --cache-embedded-node-name also makes logs and Grafana dashboards much easier to read, because otherwise each node gets a random name on every start.

Edit the cache configuration file

Keycloak ships a minimal Infinispan file at conf/cache-ispn.xml, and on a current release (we checked 26.8) it contains little more than the cache container and transport settings. Since Keycloak 26.3 you no longer need to edit it to keep session caches safe. Keycloak creates all the caches it needs with the expected configuration at startup. The supported way to change cache sizes is now through cache-embedded-* options rather than XML.

Set cache sizes with max-count options

Each cache with a configurable size has an option in the form --cache-embedded-<cache-name>-max-count. The caches you can size are realms, users, authorization, keys, crl, sessions, clientSessions, offlineSessions and offlineClientSessions. For example:

bin/kc.sh start \
  --cache-embedded-users-max-count=20000 \
  --cache-embedded-realms-max-count=40000 \
  --cache-embedded-offline-sessions-max-count=1000

The same options work as environment variables, such as KC_CACHE_EMBEDDED_USERS_MAX_COUNT=20000. You cannot set a maximum size on the actionTokens, authenticationSessions, loginFailures or work caches, and a maximum size for sessions and clientSessions is not supported in the deprecated volatile sessions mode.

The sizing guide gives one concrete rule for when to raise these limits. When more than 2,500 different clients make requests concurrently, not all client data fits in the default 10,000-entry caches, and the database becomes a bottleneck as client data is reloaded over and over. In that case, raise the users cache by two times the number of concurrently used clients and the realms cache by four times that number. More generally, a bigger cache means fewer database reads but more heap, so size it against your data and confirm the change with the hit-ratio metric described below.

When to use a custom cache file

You can still load your own Infinispan file with --cache-config-file, and the path can be absolute or relative to the conf/ directory:

bin/kc.sh start --cache-config-file=my-cache-file.xml

The caching guide is clear that overriding Keycloak’s default cache definitions through XML is technically possible but not supported, and it recommends doing it only when the defaults are proven to be a problem. If you do override them, --cache-config-mutate=true suppresses the warning Keycloak logs about modified defaults. Since Keycloak 25, cache, cache-stack and cache-config-file are runtime options, so you no longer need to run kc.sh build again after changing them.

Managed identity, built on open source

Skycloak runs real upstream Keycloak for you, so you get SSO, MFA and SCIM without operating the infrastructure. Unlimited users on every plan, no fork, and you can export at any time.

Securing cache traffic between Keycloak nodes

Cache traffic carries session and user data, so it should be encrypted. On TCP-based stacks, which includes the default jdbc-ping, Keycloak encrypts it for you. It generates a self-signed RSA 2048-bit certificate, stores the keys in the database so every node can use them, and uses TLS 1.3 with mutual authentication between nodes. The certificate is valid for 60 days and rotated every 30 days by default, and cache-embedded-mtls-rotation-interval-days changes that interval.

There are three situations where you need to do something yourself:

  • You use a UDP-based stack. Set cache-embedded-mtls-enabled=false and configure JGroups encryption by hand, following the JGroups encryption documentation. Moving to jdbc-ping is the simpler fix.
  • You run in a service mesh such as Istio. The mesh’s own mTLS can interfere with Keycloak’s, which shows up as JGRP000006: failed accepting connection from peer SSLSocket and a cluster that never forms. Either set port 7800 to PERMISSIVE in a PeerAuthentication resource, or turn off Keycloak’s mTLS and let the mesh encrypt and authorize the traffic.
  • Your policy requires your own certificates. Use cache-embedded-mtls-key-store-file, cache-embedded-mtls-key-store-password, cache-embedded-mtls-trust-store-file and cache-embedded-mtls-trust-store-password. The guide recommends a separate self-signed certificate for each Keycloak deployment.

Network segmentation still matters on top of encryption: only Keycloak nodes should be able to reach ports 7800 and 57800.

Check cache status and fix common issues

There are three places to confirm that a cluster has formed:

  1. The logs. Look for ISPN000094, which Infinispan logs whenever the membership changes. A healthy two-node cluster looks like ISPN000094: Received new cluster view for channel ISPN: [node1-26186|1] (2) [node1-26186, node2-37007], where (2) is the member count. You may also see ISPN100002: Starting rebalance with members, which appears when the cluster redistributes entries after a node joins or leaves.
  2. The admin console. In the master realm, open the user menu, choose Realm info, then the Provider info tab, and expand the connectionsInfinispan entry with Show more to see the cluster status and the health of each cache.
  3. Metrics. The vendor_cluster_size metric should equal the number of Keycloak instances you expect.

To see the full Infinispan configuration a node is actually running with, start it with --log-level=info,org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory:debug.

These are the problems we see most often and what usually causes them:

Symptom Likely cause Fix
Each node reports a cluster size of 1 Ports 7800 or 57800 blocked, or the bind address is not reachable Open the ports between nodes and set cache-embedded-network-bind-address
Two environments merge into one cluster Both use the same database Give each environment its own database, or enable --features=stateless and give each a unique cluster name
JGRP000006 errors in a service mesh Mesh mTLS conflicts with Keycloak’s mTLS Make port 7800 PERMISSIVE or disable one of the two
Admin changes appear late on some nodes Invalidation messages are not reaching all nodes Fix the cluster first, because invalidations travel over the same transport
High database load after a restart Caches start empty and fill on demand This is expected, so size for the cold-start period and not only the steady state

You can also clear caches by hand, which is useful after editing data directly in the database. In the admin console of the master realm, the user menu has a Clear Caches entry for administrators, and the Admin REST API exposes POST /admin/realms/{realm}/clear-realm-cache, clear-user-cache, clear-keys-cache and clear-crl-cache.

Monitor Keycloak cache performance with metrics

Since Keycloak 25, cache metrics are exposed automatically whenever metrics are enabled, so you do not need a separate cache metrics switch. Turn on metrics with --metrics-enabled=true, and add --cache-metrics-histograms-enabled=true if you also want latency histograms, keeping in mind that the Keycloak metrics guide warns histograms have a performance cost on a saturated system.

bin/kc.sh start --metrics-enabled=true --cache-metrics-histograms-enabled=true

The most useful number is the cache hit ratio, which tells you how often Keycloak found what it needed in memory. For the local caches you can compute it in Prometheus with cache_gets_total{result="hit"} / cache_gets_total, and cache_size shows how many entries each cache currently holds. A hit ratio that stays low while a cache sits at its maximum size is the clearest sign that its max-count is too small. Our post on authentication metrics and KPIs covers which other Keycloak metrics are worth alerting on.

Keycloak caching on Skycloak

If you would rather not tune Infinispan yourself, Skycloak runs managed Keycloak clusters for you, and we operate the clustering and cache configuration as part of the service. You choose the size and region when you create a cluster, and the Insights dashboard shows your logins, failures and active users over time. For self-managed clusters, our Keycloak cluster configuration best practices post covers the settings around caching, such as database pooling and sticky sessions.

FAQ

What is KC_CACHE_STACK in Keycloak?

KC_CACHE_STACK is the environment variable for the --cache-stack option, which selects the JGroups transport stack Keycloak nodes use to discover each other and exchange cache data. Since Keycloak 26.1 the default is jdbc-ping, which uses the shared database for discovery and TCP for traffic. Every other value, including kubernetes, is deprecated, so on current versions you should leave it unset.

What is the difference between KC_CACHE=ispn and KC_CACHE=local?

KC_CACHE=ispn turns on distributed Infinispan caching so several Keycloak nodes can form a cluster, and it is the default for kc.sh start. KC_CACHE=local keeps every cache on the local node only, which is what kc.sh start-dev uses. Use local only for development or a single test instance.

Does Keycloak need Redis or an external cache?

No. A standard Keycloak cluster uses the Infinispan caches embedded in each Keycloak process and the shared database, with no separate cache server. An external Infinispan server is only used by the deprecated multi-cluster v1 (multi-site) setup, while the supported multi-cluster v2 setup uses the stateless feature and the database instead. Redis is not supported by upstream Keycloak, although we tested a Redis-backed alternative in Keycloak on Redis: Locke vs embedded Infinispan.

How do I clear the Keycloak cache?

As an administrator in the master realm, open the user menu in the admin console and choose Clear Caches, which can clear the realm, user, keys and CRL caches. You can do the same through the Admin REST API with POST /admin/realms/{realm}/clear-realm-cache or clear-user-cache. Clearing is only needed when data changed outside Keycloak, because normal admin changes invalidate the caches automatically.

Do I need sticky sessions with distributed caching?

Keycloak works without them, because authentication sessions live in a distributed cache that every node can read. The caching guide still recommends session affinity in production, since sending a user back to the node that created their login avoids moving session state between nodes and saves CPU, memory and network traffic.

Conclusion

On Keycloak 26.x, a good cache setup mostly means trusting the defaults: run kc.sh start, leave KC_CACHE_STACK unset so nodes use jdbc-ping, open ports 7800 and 57800 between nodes and keep each environment on its own database. From there, size memory with the official formula, raise the users and realms caches with cache-embedded-*-max-count options when the hit ratio tells you to, and keep the built-in mTLS on. If you would rather have someone else run the cluster, you can see Skycloak’s plans and start with a managed Keycloak cluster that already has this configured.

Filed under

Managed identity, built on open source

Skycloak runs real upstream Keycloak for you, so you get SSO, MFA and SCIM without operating the infrastructure. Unlimited users on every plan, no fork, and you can export at any time.

Guilliano Molaire
Written by
Founder, Skycloak

Guilliano is the founder of Skycloak and a cloud infrastructure specialist with deep expertise in product development and scaling SaaS products. He discovered Keycloak while consulting on enterprise IAM and built Skycloak to make managed Keycloak accessible to teams of every size.

© 2026 Skycloak. All Rights Reserved. Design by Yasser SolimanKeycloak™ is a trademark of the Linux Foundation. Skycloak is not affiliated with the Keycloak project.