Keycloak

Run Keycloak as the identity hub for internal apps. This page explains the objects you actually configure, how to automate them, and what breaks in production.

If you only remember six things
  • A realm is a security boundary, not an app folder. Most internal estates want a small number of realms, not one per service.
  • Clients represent applications, groups represent humans, and roles represent permissions. Mixing those concepts early makes later automation miserable.
  • User federation keeps LDAP as the source of truth; identity brokering delegates login to another IdP. They solve different problems.
  • Most "OIDC is broken" incidents are actually hostname, reverse-proxy, or cookie issues.
  • A database backup is your real backup. Realm export is useful for migration, review, and smoke tests, but it is not disaster recovery by itself.
  • Clock skew between browser, reverse proxy, Keycloak, and upstream IdP produces weird, intermittent failures. Keep NTP boring.

Mental model

Keycloak is the control plane for authentication, tokens, and authorization metadata. It is not your user database unless you explicitly decide it should be. In most enterprise installs it fronts a directory like FreeIPA or another LDAP source, and it publishes OIDC or SAML to applications.

ObjectWhat it isCommon mistake
RealmIsolation boundary for users, sessions, keys, flows, and clientsCreating one realm per app when one shared workforce realm would do
ClientAn application or service that trusts KeycloakTreating a client like a user group
Client scopeA reusable bundle of protocol mappers and token settingsCopy-pasting mappers into every client instead of using scopes
Authentication flowThe step-by-step login pipelineEditing the built-in browser flow directly instead of copying it first
Realm rolePermission that makes sense across many appsUsing realm roles for app-specific privileges
Client rolePermission meaningful only to one clientReusing the same app-specific role names across unrelated clients
Windows-specific federation walkthrough: if your admin workstation is Windows and your source directory is FreeIPA over LDAPS, use Keycloak on Windows + LDAPS for the field-by-field setup. This page stays platform-neutral.

Production topology

The default production shape is simple: TLS terminates at a reverse proxy or load balancer, one or more Keycloak nodes serve HTTP on the inside, and a shared Postgres database stores persistent state. Put all nodes behind the same external hostname and keep the reverse proxy headers correct.

# /etc/keycloak/conf/keycloak.conf
db=postgres
db-url=jdbc:postgresql://pgbouncer.internal:6432/keycloak
db-username=keycloak
db-password=${KEYCLOAK_DB_PASSWORD}

hostname=https://sso.example.internal
hostname-strict=true
proxy-headers=xforwarded

http-enabled=true
http-port=8080

health-enabled=true
metrics-enabled=true
log-level=info,org.keycloak.events:debug

This configuration assumes TLS offload at the proxy. Keycloak still serves HTTP internally on :8080, but it generates links and cookies for the external HTTPS hostname. If that external URL does not exactly match what users hit in the browser, redirect loops start.

upstream keycloak_pool {
    server kc01.internal:8080;
    server kc02.internal:8080;
}

server {
    listen 443 ssl http2;
    server_name sso.example.internal;

    ssl_certificate     /etc/pki/tls/certs/sso.example.internal.crt;
    ssl_certificate_key /etc/pki/tls/private/sso.example.internal.key;

    location / {
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Host $host;
        proxy_http_version 1.1;
        proxy_pass http://keycloak_pool;
    }
}

Realms, clients, and flows

Most day-to-day Keycloak work is one of three things: create a realm, onboard a client, or customize a login flow. Keep them separate in your head and in automation.

Realm layout

A common pattern is one workforce realm for human users and one machine realm only if you truly need separation of keys, tokens, and policies. Inside the realm, standardize client scopes for profile, email, groups, and team metadata so every app does not reinvent token mapping.

Client example

{
  "clientId": "grafana",
  "name": "Grafana",
  "protocol": "openid-connect",
  "publicClient": false,
  "secret": "replace-me",
  "redirectUris": [
    "https://grafana.example.internal/login/generic_oauth"
  ],
  "webOrigins": [
    "https://grafana.example.internal"
  ],
  "standardFlowEnabled": true,
  "directAccessGrantsEnabled": false,
  "serviceAccountsEnabled": false,
  "attributes": {
    "post.logout.redirect.uris": "https://grafana.example.internal/*"
  }
}

For browser apps, leave standardFlowEnabled=true and lock down redirect URIs. Avoid wildcards broader than the exact host and path prefixes you really need.

Flows

Good habit: copy the built-in browser flow, name the copy for the realm, and change the copy. Upgrades are easier when the stock flow still exists untouched.

Roles, groups, and service accounts

Authorization usually gets messy faster than authentication. The clean model is:

export KC_URL="https://sso.example.internal"

# Log in once as an admin
kcadm.sh config credentials --server "$KC_URL" --realm master --user admin

# Create a realm role
kcadm.sh create roles -r platform -s name=platform-admin

# Create a client role
CLIENT_ID=$(kcadm.sh get clients -r platform -q clientId=grafana --fields id --format csv --noquotes)
kcadm.sh create clients/$CLIENT_ID/roles -r platform -s name=editor

# Grant a realm role to a user
kcadm.sh add-roles -r platform --uusername alice --rolename platform-admin

When multiple apps need the same entitlement, do not stamp the same client role into each client. Create one realm role, then map it into client roles or token claims as needed.

Federation and identity brokering

User federation means Keycloak reads users from LDAP or another external store. Identity brokering means Keycloak delegates login to some other IdP over OIDC or SAML. In practice you often use one or the other, not both, for the same workforce.

If the source of truth is...Use...Typical example
An LDAP directory you operateUser federationFreeIPA, OpenLDAP & 389DS, Active Directory
An external OIDC/SAML providerIdentity brokeringAzure AD, Entra ID, Okta, another Keycloak

Example LDAP federation component for a FreeIPA-style directory:

{
  "name": "freeipa-ldap",
  "providerId": "ldap",
  "providerType": "org.keycloak.storage.UserStorageProvider",
  "parentId": "platform",
  "config": {
    "enabled": ["true"],
    "priority": ["0"],
    "editMode": ["READ_ONLY"],
    "cachePolicy": ["DEFAULT"],
    "importEnabled": ["true"],
    "connectionUrl": ["ldaps://ipa.example.internal:636"],
    "usersDn": ["cn=users,cn=accounts,dc=example,dc=internal"],
    "bindDn": ["uid=keycloak-bind,cn=users,cn=accounts,dc=example,dc=internal"],
    "bindCredential": ["replace-me"],
    "usernameLDAPAttribute": ["uid"],
    "rdnLDAPAttribute": ["uid"],
    "uuidLDAPAttribute": ["ipaUniqueID"],
    "userObjectClasses": ["inetOrgPerson, organizationalPerson, person, ipaObject"]
  }
}
kcadm.sh create components -r platform -f ldap-freeipa.json

For a Windows-hosted install and LDAPS truststore setup, see Keycloak on Windows + LDAPS. For directory layout and replication trade-offs, see OpenLDAP & 389DS.

Admin CLI examples

kcadm.sh is the repeatable way to manage Keycloak. Use the UI for discovery, then convert anything worth keeping into CLI or JSON so it can be recreated. On Windows the equivalent binary is kcadm.bat.

export KC_URL="https://sso.example.internal"
export KC_REALM="platform"

# Authenticate
kcadm.sh config credentials --server "$KC_URL" --realm master --user admin

# Create the realm if it does not already exist
kcadm.sh create realms -s realm="$KC_REALM" -s enabled=true

# Create a client from JSON
kcadm.sh create clients -r "$KC_REALM" -f grafana-client.json

# Look up the UUID and fetch the client secret
CLIENT_ID=$(kcadm.sh get clients -r "$KC_REALM" -q clientId=grafana --fields id --format csv --noquotes)
kcadm.sh get clients/$CLIENT_ID/client-secret -r "$KC_REALM"

# Create a user and set an initial password
kcadm.sh create users -r "$KC_REALM" -s username=bob -s enabled=true -s email=bob@example.internal
USER_ID=$(kcadm.sh get users -r "$KC_REALM" -q username=bob --fields id --format csv --noquotes)
kcadm.sh set-password -r "$KC_REALM" --userid "$USER_ID" --new-password 'ChangeMeNow!'
Automation pattern: store client definitions, flows, and mapper JSON in git; apply them in CI after Keycloak starts. That keeps rebuilds and upgrades honest.

Backups and upgrades

Treat Keycloak as three things you must preserve together: the database, local customizations, and the exact release/version you are running.

# 1. Database backup (this is the real backup)
pg_dump -Fc -h pgbouncer.internal -U keycloak keycloak \
  -f /srv/backups/keycloak-$(date +%F).dump

# 2. Realm export for review or migration tests
/opt/keycloak/bin/kc.sh export --dir /srv/exports/keycloak-$(date +%F) \
  --realm platform --users same_file

# 3. Preserve local additions
tar czf /srv/backups/keycloak-customizations-$(date +%F).tgz \
  /opt/keycloak/conf /opt/keycloak/themes /opt/keycloak/providers

Upgrade sequence that fails the least:

  1. Read release notes for the target version and confirm supported DB and Java versions.
  2. Back up the DB and customizations.
  3. Stand up the new version alongside the old one if possible.
  4. Run kc.sh build with the new config and providers.
  5. Smoke-test one browser login, one client-credentials flow, one admin CLI operation, and one LDAP-federated login before calling it done.
Realm export is not full disaster recovery. It does not replace the database backup, and it will not preserve every operational detail the same way a restored DB will.

Troubleshooting

SymptomLikely causeFix
Browser bounces between the app and Keycloak, never logs inhostname, reverse proxy, or redirect URI mismatchSet Keycloak's external hostname exactly, pass X-Forwarded-Proto=https, and make the client redirect URI match what the browser actually uses.
Invalid parameter: redirect_uriClient redirect URIs are too strict or simply wrongInspect the exact URL sent by the app and register only that hostname/path prefix.
Login page loads but session cookies never stickMixed HTTP/HTTPS, wrong host, or proxy headers missing so cookies are marked for the wrong originUse one canonical HTTPS URL, confirm the browser sees Secure cookies for the expected domain, and verify proxy headers.
invalid_code, intermittent login failures, or random token exchange errorsClock skew between Keycloak, proxy, app, or upstream IdPFix NTP everywhere. Start with Chrony and confirm time on all nodes before changing flows.
Admin CLI gets 401 UnauthorizedWrong admin realm, expired token, or bad server URLRe-run kcadm.sh config credentials against the right server and realm, then retry.
LDAP users can search but cannot log inBind account, user filter, password policy, or mapper issue in federationCheck the provider tests, then use Keycloak on Windows + LDAPS or OpenLDAP & 389DS to debug the directory side.
Post-upgrade themes or providers disappearCustom files were not copied into the new install or the build step was skippedRestore themes/ and providers/, then rerun kc.sh build.