Keycloak just shipped version 26.7.4 on September 16, 2026, and the timing matters more than usual. Red Hat and the Keycloak team spent most of 2026 patching a run of serious flaws, including an unauthenticated account-takeover bug (CVE-2026-18963) that let attackers bypass the reset-credentials flow entirely. If you are standing up single sign-on (SSO) for the first time, or migrating off a paid identity provider, the setup choices you make in the first hour, things like redirect URI patterns, admin role scope, and database persistence, decide whether you inherit those problems or avoid them. This tutorial walks through a secure, production-realistic Keycloak deployment from a blank Linux box to a working realm with hardened defaults, step by step.
Keycloak is the open-source identity and access management (IAM) platform originally built by Red Hat, now maintained as an independent open-source community project. It handles OAuth 2.0, OpenID Connect (OIDC), and SAML 2.0 out of the box, which is why teams reach for it instead of paying per-user fees to Okta or Auth0. The tradeoff is that you own the operations: patching, database backups, clustering, and the security hardening that a SaaS vendor would otherwise handle for you. That tradeoff is exactly what this guide addresses.
Don't miss new tech stories on Google
Add Tech Insider once in the Google app and our stories appear in your news suggestions.
What Is Keycloak and Why Teams Self-Host It in 2026
Keycloak centralizes login for every application in an organization behind one identity provider. Instead of each app storing its own password database, apps redirect users to Keycloak, which authenticates them and hands back a signed token. That token carries roles, group membership, and custom claims that downstream services trust without a second login. It is the same pattern Auth0 and Okta sell as a managed service, except Keycloak ships the entire stack as free, source-available software distributed under the Apache 2.0 license.
The pull toward self-hosting has grown alongside two 2026 pressures: rising per-user SaaS identity pricing at scale, and compliance teams that want authentication data to stay inside their own network boundary rather than a third-party cloud. Keycloak supports realms (isolated tenant boundaries), clients (individual applications), and federated identity, so a single deployment can serve an internal admin panel, a customer-facing web app, and a mobile client, each with different token lifetimes and authentication requirements, from one control plane.
What changed in the last few months is the security posture conversation. Red Hat’s security advisories for Keycloak in 2026 disclosed a cluster of vulnerabilities across the 26.x line: a Time-of-check-to-time-of-use flaw that let an admin with narrow manage-clients permission escalate to full realm-admin (CVE-2026-9796), a predictable account-linking hash that enabled account takeover through a malicious OIDC client (CVE-2026-15571), and a Resource Owner Password Credentials bypass that issued tokens even when that grant type was explicitly disabled (CVE-2026-9792). None of these are exotic; they are the kind of default-configuration traps a rushed setup walks straight into. This guide builds the hardening in from step one instead of bolting it on afterward.
Realms, Clients, and Identity Brokering Explained
Three terms drive nearly every decision in a Keycloak deployment, and getting them straight before you start saves a lot of backtracking later. A realm is a fully isolated management boundary, users, roles, and configuration in one realm are invisible to another, which is why most organizations run separate realms for staging and production rather than relying on naming conventions inside a single realm. A client is one application registered to authenticate through that realm; a company with a marketing site, an internal dashboard, and a mobile app typically registers three separate clients inside one realm, each with its own redirect rules and token settings. Identity brokering is the layer that lets a realm delegate authentication outward, so a user can sign in with an existing Google or GitHub account, or a corporate Active Directory identity, while Keycloak still issues the final token your applications trust.
Build vs. Buy vs. Self-Host: Where Keycloak Fits
Most teams evaluating Keycloak are choosing between three paths: building authentication from scratch with a library like Passport.js or Spring Security, paying a managed vendor per active user, or operating an open-source identity server themselves. Building from scratch is rarely worth it past a handful of users, since correctly implementing OIDC, token rotation, and password storage is a security-critical project in its own right, not a weekend task. Managed vendors remove that burden entirely but tie recurring cost to user growth in a way that gets expensive fast for consumer-facing products. Keycloak sits between those two: the protocol implementation is handled for you and battle-tested against real production traffic across thousands of deployments, but the hosting, scaling, and patching responsibility stays with your team.
Prerequisites: What You Need Before You Start
Gather these before touching a terminal. Version mismatches are the single biggest source of setup failures reported in Keycloak community threads, so confirm each one explicitly rather than assuming your system defaults are close enough.
| Requirement | Minimum Version / Spec | Notes |
|---|---|---|
| Keycloak | 26.7.4 (latest as of Sept. 16, 2026) | Pull the exact tag, never latest, so upgrades are deliberate |
| Container runtime | Docker 27+ or Podman 5+ | Either works; this guide uses Docker Compose syntax |
| Database | PostgreSQL 16 or 17 | Production deployments should never run on the embedded dev database |
| Java runtime (bare-metal installs only) | OpenJDK 21 LTS | Only needed if you skip containers and run the ZIP distribution directly |
| RAM | 2 GB minimum, 4 GB+ recommended | Infinispan caching and the admin console both hold memory |
| Disk | 10 GB free | Logs, database volume, and theme assets add up over time |
| Domain + TLS certificate | Any valid cert (Let’s Encrypt is fine) | Production mode refuses to start cleanly without HTTPS configured |
| OS | Ubuntu 22.04/24.04, RHEL 9, or Debian 12 | Any modern Linux distribution with systemd works |
You will also want a spare subdomain (something like auth.yourcompany.com) pointed at the host you are provisioning. Keycloak’s issuer URL, the value embedded in every token it signs, has to match the hostname clients actually use to reach it, or token validation breaks in ways that are miserable to debug after the fact.
Step 1: Provision the Host and Update the System
Start from a clean, fully patched Linux host. A 2 vCPU / 4 GB RAM cloud instance is enough for a single-node evaluation or a small production deployment behind a few dozen applications.
sudo apt update && sudo apt upgrade -y
sudo apt install -y curl ca-certificates gnupg ufw
sudo hostnamectl set-hostname auth-01
Set the firewall to allow only what Keycloak and your admin access actually need before you open anything else. Leaving the admin console reachable from the open internet during setup is one of the most common ways teams get compromised before they even finish configuring the realm.
sudo ufw allow OpenSSH
sudo ufw allow 443/tcp
sudo ufw deny 8080/tcp
sudo ufw enable
Step 2: Install Docker and Docker Compose
Running Keycloak as a container keeps upgrades reproducible and avoids polluting the host with a Java installation you have to manage separately.
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo usermod -aG docker $USER
newgrp docker
docker --version
docker compose version
Confirm both commands return a version string before moving on. If docker compose version fails, the Compose plugin did not install correctly and none of the following steps will work as written.
Step 3: Create a Persistent PostgreSQL Database
Never point Keycloak at its embedded H2 database outside of a throwaway local test. The embedded database does not survive a container restart reliably and is explicitly unsupported for production traffic. Set up a dedicated PostgreSQL instance with a persistent volume.
mkdir -p ~/keycloak-stack && cd ~/keycloak-stack
cat > docker-compose.yml <<'EOF'
version: "3.9"
services:
postgres:
image: postgres:17
restart: unless-stopped
environment:
POSTGRES_DB: keycloak
POSTGRES_USER: keycloak
POSTGRES_PASSWORD_FILE: /run/secrets/pg_password
volumes:
- pg_data:/var/lib/postgresql/data
secrets:
- pg_password
networks:
- kc_net
volumes:
pg_data:
secrets:
pg_password:
file: ./secrets/pg_password.txt
networks:
kc_net:
driver: bridge
EOF
mkdir -p secrets
openssl rand -base64 24 > secrets/pg_password.txt
chmod 600 secrets/pg_password.txt
Using a Docker secret file instead of a plaintext environment variable keeps the database password out of docker inspect output and shell history, a small step that closes off an easy credential-leak path.
Step 4: Generate Bootstrap Credentials and TLS Certificates
Keycloak 26.x requires an explicit bootstrap admin username and password set through environment variables (the old KEYCLOAK_ADMIN variable name was deprecated in favor of KC_BOOTSTRAP_ADMIN_USERNAME). Generate a strong password rather than typing something memorable, since this account only exists to create your real administrative users.
openssl rand -base64 20 > secrets/kc_admin_password.txt
chmod 600 secrets/kc_admin_password.txt
echo "admin_boot" > secrets/kc_admin_user.txt
For TLS, use Let’s Encrypt via Certbot if you are terminating HTTPS on the Keycloak host directly, or terminate at a reverse proxy (Nginx, Traefik, or Caddy) sitting in front of the container. Reverse-proxy termination is the more common production pattern because it lets you rotate certificates without touching the identity server itself.
sudo apt install -y certbot
sudo certbot certonly --standalone -d auth.yourcompany.com \
--non-interactive --agree-tos -m [email protected]
Step 5: Add the Keycloak Service to Docker Compose
Append the Keycloak service definition, pinned to the exact 26.7.4 tag, connected to the PostgreSQL container over the internal Docker network rather than an exposed port.
cat >> docker-compose.yml <<'EOF'
keycloak:
image: quay.io/keycloak/keycloak:26.7.4
restart: unless-stopped
command: start --optimized
environment:
KC_DB: postgres
KC_DB_URL: jdbc:postgresql://postgres/keycloak
KC_DB_USERNAME: keycloak
KC_DB_PASSWORD_FILE: /run/secrets/pg_password
KC_BOOTSTRAP_ADMIN_USERNAME_FILE: /run/secrets/kc_admin_user
KC_BOOTSTRAP_ADMIN_PASSWORD_FILE: /run/secrets/kc_admin_password
KC_HOSTNAME: auth.yourcompany.com
KC_PROXY_HEADERS: xforwarded
KC_HTTP_ENABLED: "false"
volumes:
- /etc/letsencrypt/live/auth.yourcompany.com:/etc/x509/https:ro
ports:
- "127.0.0.1:8443:8443"
secrets:
- pg_password
- kc_admin_user
- kc_admin_password
networks:
- kc_net
depends_on:
- postgres
EOF
sed -i '/secrets:/a\ kc_admin_user:\n file: ./secrets/kc_admin_user.txt\n kc_admin_password:\n file: ./secrets/kc_admin_password.txt' docker-compose.yml
Binding the port to 127.0.0.1 keeps Keycloak reachable only through your reverse proxy, not directly from the internet. The proxy handles the public-facing 443 port and forwards traffic internally, which is the same pattern any managed IAM vendor uses behind the scenes.
Step 6: Start the Stack and Verify the Admin Console
docker compose up -d
docker compose logs -f keycloak
Watch for the line confirming Keycloak has started and is listening on the configured port. A typical successful boot looks like this in the logs:
2026-09-28 14:02:11,884 INFO [io.quarkus] (main) Keycloak 26.7.4 on JVM started in 8.941s.
Listening on: https://0.0.0.0:8443
Profile prod activated.
Installed features: [agroal, cdi, hibernate-orm, jdbc-postgresql, keycloak, ...]
Once that appears, point your reverse proxy at 127.0.0.1:8443 and load https://auth.yourcompany.com in a browser. You should see the Keycloak welcome page, not the admin console directly, since the root path is intentionally not the login screen.
Step 7: Log In and Immediately Rotate the Bootstrap Account
Navigate to /admin and sign in with the bootstrap credentials from Step 4. The very first action inside the console should be creating a named, individually-attributable admin user in the master realm, then disabling the shared bootstrap account. Sharing one bootstrap login among a team is exactly the kind of practice that makes post-incident audits impossible, since there is no way to tell which person performed which administrative action.
- Go to Users → Add user, create an account with your real name and a unique username
- Assign the new user the
adminrole under Role mapping - Log out, log back in as the new user to confirm access
- Return to the original bootstrap account and disable it (do not delete it yet, in case you need a recovery path)
Step 8: Create Your Application Realm
Never configure application clients directly in the master realm, that realm exists purely to administer Keycloak itself. Create a dedicated realm per environment or product line so a misconfiguration in one does not cascade into another.
- Click the realm selector in the top-left, then "Create realm"
- Name it something environment-specific, like
company-prod - Under Realm settings → General, confirm the "Enabled" toggle is on
- Under Realm settings → Tokens, set a reasonable access token lifespan (5–15 minutes is standard; avoid multi-hour defaults)
Step 9: Register Your First Client Application
A client in Keycloak represents one application requesting authentication. Browser-based apps (React, Vue, plain HTML/JS) should be registered as public clients using the Authorization Code flow with PKCE, since they cannot safely store a client secret. Backend services calling Keycloak server-to-server should be registered as confidential clients with a secret.
- Clients → Create client
- Client ID:
web-app, Client type: OpenID Connect - Capability config: turn on "Standard flow", leave "Direct access grants" off unless you have a specific legacy need for it
- Valid redirect URIs: enter the exact callback path, e.g.
https://app.yourcompany.com/callback, never a bare wildcard likehttps://app.yourcompany.com/* - Web origins: set to your app's exact origin for CORS, not
+or*
The redirect URI field deserves extra caution. Red Hat's advisory for CVE-2026-3872 documented an access-token theft path that relied on a loosely scoped wildcard redirect pattern combined with another path on the same web server. Exact-match redirect URIs close that entire class of issue.
Step 10: Configure Roles, Groups, and Client Scopes
This is where most first-time Keycloak setups get tangled, because the platform separates four related but distinct authorization concepts, and none of them are interchangeable.
| Concept | Scope | Typical use |
|---|---|---|
| Realm role | Applies across every client in the realm | Broad categories like employee or customer |
| Client role | Scoped to one specific client/application | App-specific permissions like billing-admin |
| Group | Collection of users that inherits assigned roles | Org-chart style grouping, e.g. finance-team |
| Client scope | Set of protocol mappers controlling what claims appear in a token | Deciding whether email or groups shows up in the JWT |
If a claim you expect, like a group membership or custom attribute, is missing from the issued token, the fix is almost always a missing protocol mapper on a client scope, not a role assignment problem. Check Client scopes → your scope → Mappers before assuming the role configuration is broken.
Step 11: Enable Multi-Factor Authentication
Keycloak ships built-in TOTP support without any extension. Turning it on for administrative accounts is non-negotiable given how many 2026 advisories centered on privilege escalation once an attacker gained any foothold.
- Authentication → Flows, locate "Browser" flow
- Confirm "OTP Form" is present and set to Required for admin-facing flows, or Conditional for general users
- Under Realm settings → Security defenses, review "Brute force detection" and enable it with a lockout threshold
For realms serving external customers, conditional MFA that triggers based on risk (new device, new location) balances security against login friction better than a blanket requirement.
Step 12: Connect a Frontend Application
With the realm and client configured, wire up a frontend using the standard OIDC Authorization Code + PKCE flow. Most frameworks have a maintained adapter; here is a minimal vanilla example using the keycloak-js adapter that ships from the Keycloak project itself.
import Keycloak from "keycloak-js";
const keycloak = new Keycloak({
url: "https://auth.yourcompany.com",
realm: "company-prod",
clientId: "web-app",
});
keycloak.init({ onLoad: "login-required", pkceMethod: "S256" })
.then((authenticated) => {
if (authenticated) {
console.log("Token expires in:", keycloak.tokenParsed.exp);
}
})
.catch((err) => console.error("Keycloak init failed", err));
// Refresh the token before it expires
setInterval(() => {
keycloak.updateToken(30).catch(() => keycloak.login());
}, 20000);
The pkceMethod: "S256" line matters. Without it, public clients fall back to weaker flow behavior in some adapter configurations, which is exactly the pattern current OIDC best practice has moved away from across the industry.
Step 13: Apply Production Hardening and Patch Against 2026 CVEs
Before calling the deployment done, work through the hardening checklist below. Every item maps to a documented 2026 advisory or a real-world misconfiguration pattern.
| Hardening step | Why it matters |
|---|---|
| Run 26.7.4 or newer, never below 26.7.2 | 26.7.2 is the fixed version for CVE-2026-18963, the unauthenticated account-takeover bug via reset-credentials-flow bypass |
| Disable Direct Access Grants (ROPC) unless required | CVE-2026-9792 showed ROPC tokens could be issued even when the grant was explicitly disabled in policy; removing it from the client entirely is the safer control |
Restrict manage-clients to a small, audited group | CVE-2026-9796 documented a TOCTOU flaw letting a manage-clients admin escalate to full realm-admin |
| Audit Admin REST API access, especially vault-resolved secrets | CVE-2026-17048 covered rotated client secrets being exposed through the Admin REST API |
| Never use wildcard redirect URIs | CVE-2026-3872 relied on a loosely scoped wildcard redirect path for access-token theft |
| Validate JWE signature policy explicitly | CVE-2026-9793 allowed unsigned claims through decrypted raw-JSON JWE requests despite a configured signature policy |
| Run behind a reverse proxy with forwarded headers set correctly | Misconfigured KC_PROXY_HEADERS produces wrong redirect URLs and can weaken cookie security |
Subscribe to the Keycloak GitHub releases page and Red Hat's security advisory feed so patch releases reach your update pipeline the same week they ship, not months later during an unrelated maintenance window.
Identity Brokering: Connecting Google, GitHub, and Enterprise Directories
Most organizations do not want to manage yet another password database if employees or customers already have an account with Google, Microsoft, GitHub, or a corporate Active Directory. Keycloak's identity brokering feature handles that delegation without changing anything on the application side, since your app still only ever talks to Keycloak and receives one consistent token format regardless of which upstream provider actually authenticated the user.
Adding a Social Login Provider
Social identity providers are the fastest brokering setup to test, since Keycloak ships built-in provider templates for the major platforms.
- Identity providers → Add provider → select Google (or GitHub, Microsoft, etc.)
- Paste in the Client ID and Client Secret generated from that provider's own developer console
- Copy the Redirect URI Keycloak generates and register it back in the provider's OAuth app settings
- Set "Trust Email" only if the upstream provider verifies email ownership, otherwise leave it off to avoid account-takeover risk through unverified addresses
Federating an Enterprise Directory via LDAP
For internal tools, many teams federate Keycloak against an existing LDAP or Active Directory instance instead of duplicating employee accounts. Under User federation → Add LDAP provider, point Keycloak at the directory's connection URL and bind credentials, then map the directory's username, email, and group attributes to Keycloak's internal user model. Once configured, employees authenticate with their existing corporate credentials, and Keycloak becomes the single OIDC-speaking front door for every internal application, without your applications ever touching LDAP directly.
Common Pitfalls When Setting Up Keycloak
These are the mistakes that show up repeatedly across setup reports and support threads, roughly in order of how often they derail a first deployment.
- Mismatched hostname and issuer URL. If
KC_HOSTNAMEdoes not exactly match the URL your applications use to reach Keycloak, token validation silently fails downstream, and the error message rarely points back to the actual cause. - Ephemeral database storage. Running the embedded dev database, or a Postgres container without a named volume, means every restart wipes realms, users, and clients. Always mount persistent storage before creating anything you care about.
- Confusing client type with client flow. A public client trying to use a client secret, or a confidential client left without one, both produce confusing authentication failures instead of a clear error.
- Missing protocol mappers. Expecting a custom attribute or group claim to appear in the token automatically, when it needs an explicit mapper added to the client scope.
- Theme changes that don't appear. Custom login themes get cached at multiple layers (browser, CDN, and Keycloak's own resource cache); a hard refresh and a Keycloak restart often both are needed after editing theme files.
- Single-node deployments treated as production-ready. One Keycloak instance is a single point of failure with no rolling-upgrade path; production traffic needs at least two nodes behind a load balancer.
- Importing a development realm JSON straight into production. Exported realm files carry test users, permissive redirect URIs, and sometimes plaintext secrets that need to be stripped or rotated first.
- Skipping database connection pool tuning. Default pool sizes are conservative; under real login traffic, an untuned pool causes timeouts that look like a Keycloak bug but are actually a database contention problem.
Keycloak vs. Auth0 vs. Okta: Choosing the Right Deployment Model
Self-hosting only makes sense if the operational tradeoff fits your team. The table below lays out the practical differences that matter when making the call.
| Factor | Keycloak | Auth0 | Okta |
|---|---|---|---|
| License cost | Free, Apache 2.0 | Usage/tier-based SaaS pricing | Per-user/contract SaaS pricing |
| Deployment | Self-hosted: VM, container, or Kubernetes | Fully managed SaaS | Fully managed SaaS |
| Infrastructure burden | You own patching, scaling, and backups | Vendor-managed | Vendor-managed |
| Customization depth | Full source access, custom providers (SPIs), themes | Rules/Actions scripting within platform limits | Limited to platform configuration options |
| Time to first login | Hours to a day for a hardened setup | Typically under an hour | Typically under an hour |
| Best fit | Teams with DevOps capacity wanting full control or data residency | Startups wanting fast time-to-market | Enterprises wanting mature governance tooling |
For a deeper breakdown of Keycloak against two other open-source alternatives, see our Keycloak vs. Ory vs. Zitadel comparison, which covers deployment footprint and community activity across all three projects.
The decision usually comes down to how your organization already staffs infrastructure work. A team that already runs its own Kubernetes clusters, manages its own PostgreSQL fleet, and has an on-call rotation for other self-hosted services absorbs Keycloak operations without much additional overhead, since the skills transfer directly. A small team without that infrastructure muscle, or one moving fast on a product launch where engineering time is the scarcest resource, often comes out ahead paying Auth0 or Okta and revisiting the self-hosting decision once user volume and cost pressure justify the switch. Neither path is universally correct; the honest framing is that Keycloak trades a recurring dollar cost for a recurring operational cost, and which one is cheaper depends entirely on the team doing the work.
Monitoring Keycloak: Health Checks, Metrics, and Logs
A Keycloak deployment that silently degrades is worse than one that fails loudly, since authentication outages block every downstream application at once. Keycloak exposes dedicated health and metrics endpoints on its management port, separate from the port handling actual login traffic, specifically so monitoring tooling does not compete with real user requests.
# Enable health checks and metrics in the Keycloak service environment
KC_HEALTH_ENABLED=true
KC_METRICS_ENABLED=true
# Endpoints exposed on the management port (default 9000)
# GET /health/ready -> readiness probe for load balancers
# GET /health/live -> liveness probe for orchestrators
# GET /metrics -> Prometheus-formatted metrics
Point Prometheus at the /metrics endpoint and build a Grafana dashboard tracking login success and failure rates, active sessions, database connection pool saturation, and JVM heap usage. A sudden spike in failed logins that does not correlate with a known deployment or password-policy change is one of the clearest early signals of a credential-stuffing attempt against the realm, and catching it in a dashboard beats discovering it in a security incident review weeks later. For log aggregation, ship Keycloak's structured JSON logs to whatever centralized logging stack your team already runs. The admin event log, tracked separately from the general application log, records every realm, client, and role change with the acting user attached, which is exactly the audit trail a compliance review will ask for.
Scaling Keycloak: Clustering and Infinispan Caching
A single Keycloak node works for development and small internal deployments, but production traffic needs at least two nodes for both availability and rolling upgrades. Keycloak coordinates session and cache state across nodes using Infinispan, its embedded distributed cache layer.
# On each node, enable clustering with a shared discovery mechanism
KC_CACHE=ispn
KC_CACHE_STACK=kubernetes # or "jdbc-ping" for non-Kubernetes environments
KC_CACHE_CONFIG_FILE=cache-ispn-jdbc-ping.xml
Outside Kubernetes, the JDBC_PING discovery mechanism lets nodes find each other through the shared PostgreSQL database rather than requiring multicast networking, which is usually blocked in cloud environments anyway. Put a load balancer with sticky sessions or a shared session cache in front of the cluster, and confirm health checks target the dedicated management port rather than the main application port.
Backing Up and Restoring a Keycloak Deployment
Since all persistent state lives in PostgreSQL, backup strategy is really database backup strategy. A minimal, cron-scheduled approach looks like this:
#!/bin/bash
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
docker compose exec -T postgres pg_dump -U keycloak keycloak \
| gzip > /backups/keycloak-${TIMESTAMP}.sql.gz
find /backups -name "keycloak-*.sql.gz" -mtime +30 -delete
Test the restore path before you need it in an actual incident, not during one. Restore the dump into a throwaway PostgreSQL instance quarterly and confirm Keycloak boots against it cleanly, including a login test against a realm you know exists in that backup.
Migrating Existing Users Into Keycloak
Teams moving off a legacy homegrown auth system or an expensive managed vendor rarely get to start with an empty user table. Keycloak supports bulk user import through its Admin REST API and through realm JSON import at startup, but the detail that trips up most migrations is password hashing. If your legacy system used bcrypt, PBKDF2, or another supported algorithm, Keycloak can import the existing password hash directly and users keep logging in with no disruption, since the hash format is preserved rather than requiring a reset. If the legacy hash algorithm is not supported, or was a custom scheme, the standard fallback is a forced password reset on next login rather than trying to reverse-engineer the original hashing logic.
# Bulk-import users via the Admin REST API (requires an access token first)
curl -X POST "https://auth.yourcompany.com/admin/realms/company-prod/users" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"username": "jane.doe",
"email": "[email protected]",
"enabled": true,
"credentials": [{
"type": "password",
"hashedSaltedValue": "",
"algorithm": "bcrypt"
}]
}'
Run migrations in batches against a staging realm first, and spot-check a sample of imported accounts by logging in with known test credentials before cutting production traffic over. A migration that silently drops group memberships or role assignments is much easier to catch against ten test accounts than against ten thousand real users on launch day.
Troubleshooting: 8 Common Keycloak Errors and Fixes
- "Invalid parameter: redirect_uri" on login. The redirect URI your app sends does not exactly match a registered value in Clients → Valid redirect URIs, including the trailing slash and protocol. Copy the exact string from your app's callback route.
- Admin console loads but login always fails. Usually a hostname mismatch. Check that
KC_HOSTNAMEin the container environment matches the URL in the browser address bar exactly, including the scheme. - "We are sorry... Invalid username or password" for a known-correct password. Brute-force detection may have temporarily locked the account after failed attempts elsewhere. Check Realm settings → Security defenses → Brute force detection for the lockout window.
- Token missing an expected custom claim. The client scope is missing a protocol mapper for that attribute. Add it under Client scopes → [scope name] → Mappers, then re-request a token.
- Container exits immediately after startup. Almost always a database connection failure. Run
docker compose logs keycloakand check for a Postgres authentication or hostname resolution error first. - Theme customizations don't render. Clear the Keycloak resource cache by restarting the container after theme file changes, and confirm the theme is selected under Realm settings → Themes, not just placed in the themes directory.
- "HTTPS required" error blocking login. Production mode enforces HTTPS by default. If you are testing behind a proxy that terminates TLS upstream, set
KC_PROXY_HEADERS: xforwardedso Keycloak trusts the forwarded protocol header instead of demanding TLS at its own socket. - Cluster nodes not finding each other. JDBC_PING discovery requires all nodes share the same database connection details and that the
KC_CACHE_STACKsetting matches across every node. A typo in one node's cache stack value silently isolates it from the cluster.
Advanced Tips for a Mature Keycloak Deployment
Once the base deployment is stable, a few additional practices separate a hobby install from something a security team would actually sign off on.
- Rotate signing keys on a schedule, not just after an incident. Realm settings → Keys lets you add a new active key while the old one remains valid for verification during the transition window.
- Externalize secrets through a vault, not environment variables in plaintext. Integrating with HashiCorp Vault for database credentials and client secrets keeps them out of your container orchestration configuration entirely.
- Layer in passwordless authentication where it fits. Keycloak supports WebAuthn for passkey-based logins, reducing exposure to credential-stuffing attacks that plague password-only realms.
- Separate the management port from the public-facing port. Health check and metrics endpoints should never be reachable from the same network path as end-user logins.
- Version-control realm exports. Export realm configuration as JSON and track it in Git so changes to roles, clients, and flows go through the same review process as application code.
- Set conservative token lifetimes and rely on refresh tokens. Short-lived access tokens (5–15 minutes) limit the blast radius of a leaked token far more effectively than long-lived tokens with no compensating controls.
Complete Working Project: Docker Compose Reference
The full stack assembled across this tutorial, ready to adapt for your own domain and secrets, looks like this:
version: "3.9"
services:
postgres:
image: postgres:17
restart: unless-stopped
environment:
POSTGRES_DB: keycloak
POSTGRES_USER: keycloak
POSTGRES_PASSWORD_FILE: /run/secrets/pg_password
volumes:
- pg_data:/var/lib/postgresql/data
secrets:
- pg_password
networks:
- kc_net
keycloak:
image: quay.io/keycloak/keycloak:26.7.4
restart: unless-stopped
command: start --optimized
environment:
KC_DB: postgres
KC_DB_URL: jdbc:postgresql://postgres/keycloak
KC_DB_USERNAME: keycloak
KC_DB_PASSWORD_FILE: /run/secrets/pg_password
KC_BOOTSTRAP_ADMIN_USERNAME_FILE: /run/secrets/kc_admin_user
KC_BOOTSTRAP_ADMIN_PASSWORD_FILE: /run/secrets/kc_admin_password
KC_HOSTNAME: auth.yourcompany.com
KC_PROXY_HEADERS: xforwarded
KC_HTTP_ENABLED: "false"
volumes:
- /etc/letsencrypt/live/auth.yourcompany.com:/etc/x509/https:ro
ports:
- "127.0.0.1:8443:8443"
secrets:
- pg_password
- kc_admin_user
- kc_admin_password
networks:
- kc_net
depends_on:
- postgres
volumes:
pg_data:
secrets:
pg_password:
file: ./secrets/pg_password.txt
kc_admin_user:
file: ./secrets/kc_admin_user.txt
kc_admin_password:
file: ./secrets/kc_admin_password.txt
networks:
kc_net:
driver: bridge
Save this alongside the backup script and the frontend snippet from Step 12, and the deployment is portable to any Docker-capable host, from a single VPS to a fleet behind a load balancer.
Frequently Asked Questions
Is Keycloak really free for production use?
Yes. Keycloak is licensed under Apache 2.0 with no per-user fee, no seat limits, and no feature paywall. The cost is operational: you handle hosting, patching, and scaling yourself, which is the tradeoff against managed platforms like Auth0 or Okta.
What's the minimum Java version Keycloak needs?
If you run Keycloak via container image, you don't manage Java at all, it's bundled inside. For the ZIP distribution running directly on a host, OpenJDK 21 LTS is the current baseline.
Can Keycloak replace both OIDC and SAML in the same deployment?
Yes, a single Keycloak realm can serve OIDC clients and SAML clients simultaneously, which is useful when migrating legacy SAML applications toward modern OIDC without a hard cutover. See our OIDC vs. SAML comparison for the protocol-level differences.
Do I need Kubernetes to run Keycloak in production?
No. Docker Compose on a couple of VMs behind a load balancer is a perfectly valid production pattern for small and mid-sized deployments. Kubernetes becomes worthwhile once you need automated scaling or are already standardized on it for other services.
How often should I patch Keycloak?
Given the volume of 2026 security advisories, check for new releases at least monthly, and treat any advisory tagged Critical or Important as an out-of-cycle patch trigger rather than waiting for the next scheduled maintenance window.
What's the difference between a realm role and a client role?
A realm role applies across every client registered in that realm, useful for broad categories like "employee." A client role is scoped to a single application, useful for permissions that only make sense inside that one app, like "billing-admin" in a finance tool.
Can Keycloak integrate with Zero Trust network access tooling?
Yes, Keycloak's OIDC tokens are commonly used as the identity layer feeding a Zero Trust access proxy, which checks the token's claims before granting network-level access. See our Zero Trust Network Access setup guide for that integration pattern.
Should I enable passwordless login instead of passwords plus MFA?
It depends on your user base's device support. Passkeys backed by WebAuthn remove password-related attack surface entirely, but rollout works best as an option alongside password-plus-MFA rather than a forced replacement on day one. Our passkeys vs. passwords vs. 2FA breakdown covers the adoption tradeoffs in more depth.





