How to Set Up Keycloak: Free SSO in 13 Steps [2026]

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.

Google · Preferred Sources

Don't miss new tech stories on Google

Add Tech Insider once in the Google app and our stories appear in your news suggestions.

Add Now

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.

RequirementMinimum Version / SpecNotes
Keycloak26.7.4 (latest as of Sept. 16, 2026)Pull the exact tag, never latest, so upgrades are deliberate
Container runtimeDocker 27+ or Podman 5+Either works; this guide uses Docker Compose syntax
DatabasePostgreSQL 16 or 17Production deployments should never run on the embedded dev database
Java runtime (bare-metal installs only)OpenJDK 21 LTSOnly needed if you skip containers and run the ZIP distribution directly
RAM2 GB minimum, 4 GB+ recommendedInfinispan caching and the admin console both hold memory
Disk10 GB freeLogs, database volume, and theme assets add up over time
Domain + TLS certificateAny valid cert (Let’s Encrypt is fine)Production mode refuses to start cleanly without HTTPS configured
OSUbuntu 22.04/24.04, RHEL 9, or Debian 12Any 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 admin role 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.

  1. Click the realm selector in the top-left, then "Create realm"
  2. Name it something environment-specific, like company-prod
  3. Under Realm settings → General, confirm the "Enabled" toggle is on
  4. 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.

  1. Clients → Create client
  2. Client ID: web-app, Client type: OpenID Connect
  3. Capability config: turn on "Standard flow", leave "Direct access grants" off unless you have a specific legacy need for it
  4. Valid redirect URIs: enter the exact callback path, e.g. https://app.yourcompany.com/callback, never a bare wildcard like https://app.yourcompany.com/*
  5. 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.

ConceptScopeTypical use
Realm roleApplies across every client in the realmBroad categories like employee or customer
Client roleScoped to one specific client/applicationApp-specific permissions like billing-admin
GroupCollection of users that inherits assigned rolesOrg-chart style grouping, e.g. finance-team
Client scopeSet of protocol mappers controlling what claims appear in a tokenDeciding 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.

  1. Authentication → Flows, locate "Browser" flow
  2. Confirm "OTP Form" is present and set to Required for admin-facing flows, or Conditional for general users
  3. 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 stepWhy it matters
Run 26.7.4 or newer, never below 26.7.226.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 requiredCVE-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 groupCVE-2026-9796 documented a TOCTOU flaw letting a manage-clients admin escalate to full realm-admin
Audit Admin REST API access, especially vault-resolved secretsCVE-2026-17048 covered rotated client secrets being exposed through the Admin REST API
Never use wildcard redirect URIsCVE-2026-3872 relied on a loosely scoped wildcard redirect path for access-token theft
Validate JWE signature policy explicitlyCVE-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 correctlyMisconfigured 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.

  1. Identity providers → Add provider → select Google (or GitHub, Microsoft, etc.)
  2. Paste in the Client ID and Client Secret generated from that provider's own developer console
  3. Copy the Redirect URI Keycloak generates and register it back in the provider's OAuth app settings
  4. 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_HOSTNAME does 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.

FactorKeycloakAuth0Okta
License costFree, Apache 2.0Usage/tier-based SaaS pricingPer-user/contract SaaS pricing
DeploymentSelf-hosted: VM, container, or KubernetesFully managed SaaSFully managed SaaS
Infrastructure burdenYou own patching, scaling, and backupsVendor-managedVendor-managed
Customization depthFull source access, custom providers (SPIs), themesRules/Actions scripting within platform limitsLimited to platform configuration options
Time to first loginHours to a day for a hardened setupTypically under an hourTypically under an hour
Best fitTeams with DevOps capacity wanting full control or data residencyStartups wanting fast time-to-marketEnterprises 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_HOSTNAME in 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 keycloak and 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: xforwarded so 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_STACK setting 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.

Related Coverage

Nadia Dubois

Nadia Dubois

AI & Innovation Editor

Nadia Dubois is the AI & Innovation Editor at Tech Insider, where she tracks the rapid evolution of artificial intelligence, from foundation models to real-world enterprise deployment. She previously covered AI and startups for La Tribune and contributed to MIT Technology Review's European coverage. Nadia specializes in generative AI, AI regulation, and the intersection of technology and European industrial policy. She holds a dual degree in Computational Linguistics and Journalism from Sciences Po Paris.

View all articles