Keycloak Memory Usage and Heap Sizing Explained
What actually consumes Keycloak memory, the documented 1250 MB baseline, the 70 percent heap rule, and how to turn login rates into node sizing.
Memory is the resource that decides how many Keycloak nodes you run. CPU scales with request rate in a fairly linear way, and the database can usually be made faster with better storage. Heap is different: it is bounded by the container limit, it fills with cache entries you did not explicitly ask for, and when it runs out the failure mode is a garbage collection stall that looks like a network problem.
This guide breaks down where Keycloak memory actually goes, what the project’s own sizing guidance says, and how to convert a login rate into a node count. Every number below comes from the Keycloak documentation rather than from any measurement taken here.
Where Keycloak Memory Goes
A Keycloak process holds four broad categories of memory.
JVM heap holds Infinispan cache entries, in-flight request state, parsed realm configuration, and the object graph for every token being issued or validated. This is the part you control with a heap flag and the part that grows with traffic.
Non-heap JVM memory covers metaspace for loaded classes, code cache for JIT-compiled methods, thread stacks, and direct byte buffers used by the HTTP layer. The Keycloak sizing guide budgets “approximately 300 MB of non-heap-based memory” per instance.
Operating system and container overhead is whatever the base image and the runtime consume outside the JVM.
Database memory, which is not on the Keycloak node at all but is frequently the thing that actually breaks under load. Sizing the node without sizing the database is a common and expensive omission.
The Documented Baseline
The Keycloak sizing guide gives a concrete starting figure: “The base memory usage for a Pod including caches of Realm data and 10,000 cached sessions is 1250 MB of RAM.”
That is a baseline, not a target. It assumes realm configuration caches are populated and that the session caches hold 10,000 entries, which is the documented default cap for the embedded caches, applied per node.
On top of that baseline, the container image applies percentage-based heap sizing rather than a fixed value. The documented defaults are -XX:MaxRAMPercentage=70 and -XX:InitialRAMPercentage=50, meaning the JVM will grow the heap to 70 percent of the container memory limit and start at 50 percent of it.
Two consequences follow directly from that.
First, if you do not set a container memory limit, the documentation warns that “the memory consumption rapidly increases as the heap size can grow up to 70 percent of the total container memory” — which on an unlimited container means 70 percent of the host. Always set a limit.
Second, the limit and the heap are not the same number. With a 2 GB limit, the JVM takes roughly 1.4 GB of heap and the documented 300 MB of non-heap sits alongside it, leaving only a few hundred megabytes of slack. That is why the container guide recommends a 2 GB memory limit for production deployments, and names 750 MB as the floor if you only want to approximate the legacy 512 MB heap default.
Working backwards, the sizing guide expresses the same relationship as a limit calculation of (total memory - 300 MB non-heap) / 0.7. If you know how much heap you need, that tells you what limit to request.
Sessions Are No Longer Purely a Heap Problem
This is the part most sizing spreadsheets get wrong, including spreadsheets written against older Keycloak releases.
The caching documentation now states that “session data are stored in the database by default and loaded on-demand” into the caches, and that the in-memory caches for user and client sessions “run with only a single owner for each cache entry.”
Both halves of that matter for sizing:
- The database is the system of record for sessions, so the cache is a read-through accelerator rather than the only copy. Losing a node no longer implies losing the sessions it owned.
- Single ownership means you do not multiply session memory by a replication factor. The older model, where each session was held by two or three owners for failover, inflated the per-session memory estimate by 2x or 3x. Applying that multiplier on a current release overstates the requirement substantially.
The practical effect is that raw session count is a weaker predictor of heap than it used to be, and the cache entry caps matter more. Those caps are set per cache with options of the form cache-embedded-sessions-max-count, defaulting to 10,000 entries.
The tradeoff is that lowering the cap saves heap and costs database reads. Raising it does the reverse. A session that misses the cache is not lost, it is fetched, which is a latency cost rather than a correctness one.
The Caches You Should Size Deliberately
Keycloak runs distributed caches for sessions, client sessions, offline sessions, offline client sessions, authentication sessions, action tokens, and login failures, plus local caches for realm and user data.
The sizing guide gives two specific tuning rules for the local caches, which are the ones that quietly cause trouble on installations with many clients:
- Increase the
userscache by “two times the number of concurrently used clients”. - Increase the
realmscache by “four times the number of concurrently used clients”.
The reason is that realm and client metadata is looked up on every token operation. If those caches are too small for the number of clients actually in play, you get a steady stream of evictions and database reads on the hot path, and the symptom presents as latency rather than as a memory alert.
The two caches worth watching separately are offline sessions and login failures. Offline sessions have long or unlimited lifespans by design, so their footprint grows with cumulative usage rather than with concurrency. Login failures accumulate under a credential stuffing attempt, which means an attack can push memory in a direction ordinary capacity planning never modelled.
Turning Load Into Nodes
The sizing guide converts request rates into vCPU directly:
- “For each 15 password-based user logins per second, allocate 1 vCPU to the cluster.”
- “For each 120 client credential grants per second, 1 vCPU to the cluster.”
- “For each 120 refresh token requests per second, 1 vCPU to the cluster.”
It also advises leaving “150% extra head-room for CPU usage to handle spikes in the load”, which is a large margin and reflects how spiky authentication traffic is at shift changes and business-day starts.
The order-of-magnitude difference between password logins and refresh grants is the important detail. A password login runs a password hashing function with a deliberately high work factor. A refresh token exchange does not. Eight times more CPU per operation is the documented gap, so a workload’s mix of logins to refreshes changes the answer more than its total request count does.
The database side has its own budget: “For every 100 login/logout/refresh requests per second: Budget for 1400 Write IOPS” and allocate “between 0.35 and 0.7 vCPU” per 100 requests per second. Write IOPS at that ratio is the figure that catches people deploying onto general purpose cloud volumes with a low baseline.
A Worked Example
Take a workload of 30 password logins per second at peak, 200 refresh token requests per second, and roughly 60,000 concurrent sessions.
- CPU from logins: 30 / 15 = 2 vCPU.
- CPU from refreshes: 200 / 120 ≈ 1.7 vCPU.
- Subtotal 3.7 vCPU, plus the documented 150 percent headroom, is roughly 9 vCPU across the cluster.
- Memory: start from the 1250 MB baseline per instance, raise the session cache caps above the 10,000 default if you want most of those 60,000 sessions served from cache, and size the container limit as
desired heap / 0.7, which is the documented(total memory - 300 MB non-heap) / 0.7restated. - Database: 230 requests per second of that traffic is login and refresh, so budget in the region of 3,200 write IOPS and 0.8 to 1.6 vCPU on the database.
Three nodes of 3 vCPU and 2 to 4 GB each satisfies the compute side and gives you rolling restarts. The Keycloak memory and heap sizing calculator on this site runs the memory half of that arithmetic interactively, including the container-limit conversion, if you want to vary the session inputs. The vCPU ratios above are the part to do on paper.
What Makes Memory Grow Unexpectedly
Several things push heap up without any change in user count:
Token bloat. Every claim you add to a token is carried in every cached session object and every request header. Trimming client scopes is the single most effective lever, and it is covered in Keycloak realms, clients and token design fundamentals.
Realm count. Each realm carries its own configuration, keys, and client set in the realm cache. Many small realms cost more resident memory than one realm with many clients, which is one more reason to avoid a realm per application.
LDAP import mode. Importing federated users writes them into the Keycloak database and populates the user cache. Read-through federation does not. The choice is usually made for correctness reasons, but it has a memory consequence.
Offline token accumulation. Long-lived offline sessions from mobile clients accumulate over months. Their eviction is governed by lifespan settings, not by concurrency.
What to Monitor
Heap utilisation on its own is a poor signal, because a healthy JVM runs near its ceiling between collections. Two better ones:
- Heap after a full collection. If the post-collection floor is trending up over days, something is retaining objects. If it is flat, high utilisation is just normal generational behaviour.
- Cache eviction and miss rates for the
users,realms, and session caches. Rising evictions on a stable user population is the early signal that the cache caps are too small for the client count, and it precedes the latency complaint by a comfortable margin.
Alert on garbage collection pause time rather than on heap percentage. A long pause is what clients experience as a timeout, and it is the metric that maps to user-visible failure.
Before You Size, Get the Deployment Shape Right
Sizing a misconfigured deployment produces a confidently wrong number. Two prerequisites are worth settling first: the hostname and proxy configuration, since a node returning 403s is not producing representative load — see Keycloak hostname and proxy errors — and the choice of identity server itself, which is worth revisiting if the estimate comes back larger than the deployment justifies. That comparison is in Keycloak vs authentik vs Authelia.
Sources
Related
Keycloak Behind a Reverse Proxy: Fix 403 and Hostname Errors
Why Keycloak returns 403 behind a proxy, rejects redirect URIs, and issues tokens with the wrong issuer, and the exact options that resolve each case.
Keycloak vs authentik vs Authelia: What to Self-Host
A protocol, architecture and footprint comparison of three self-hosted identity servers, and the deployment shapes each one is actually built for.
Keycloak Realms, Clients and Token Design Fundamentals
How Keycloak realms, clients, flows and token lifetimes fit together, plus the caching, session and clustering decisions that bite operators later on.