Skip to content

Keycloak Cheat Sheet

Overview

Keycloak is a feature-rich, open-source Identity and Access Management solution developed by Red Hat and donated to the Cloud Native Computing Foundation (CNCF) as part of the CNCF Sandbox. It provides single sign-on (SSO) capabilities, centralized user management, and authentication/authorization services that can be integrated with virtually any application. Keycloak eliminates the need for individual applications to implement their own login, registration, and security features by centralizing these concerns in a dedicated IAM platform.

Keycloak’s architecture is organized around the concept of “Realms” — isolated identity domains, each with its own users, clients, roles, and configuration. A single Keycloak instance can host multiple realms, allowing you to manage completely separate user bases (e.g., internal employees, external partners, and end customers) without interference. Within a realm, “Clients” represent applications that delegate authentication to Keycloak. Each client can be configured for different protocols (OIDC, SAML), grant types, redirect URIs, and scopes.

The platform supports standard protocols including OpenID Connect (OIDC), OAuth 2.0, and SAML 2.0, making it compatible with virtually any modern application stack. User Federation allows Keycloak to connect to external user directories like LDAP and Active Directory, synchronizing users without requiring migration. Multi-factor authentication, social login (Google, GitHub, Facebook, etc.), brute force detection, and password policies are all built in. For enterprise environments, Keycloak supports high availability clustering via Infinispan (JGroups) and can be backed by PostgreSQL, MySQL, or other JDBC-compatible databases.

Installation

Docker (Quickstart)

# Development mode (no TLS, ephemeral storage)
docker run -d \
  --name keycloak \
  -p 8080:8080 \
  -e KEYCLOAK_ADMIN=admin \
  -e KEYCLOAK_ADMIN_PASSWORD=admin \
  quay.io/keycloak/keycloak:latest \
  start-dev

# Access at http://localhost:8080/admin

Docker (Production Mode)

docker run -d \
  --name keycloak \
  -p 8443:8443 \
  -e KEYCLOAK_ADMIN=admin \
  -e KEYCLOAK_ADMIN_PASSWORD=changeme \
  -e KC_DB=postgres \
  -e KC_DB_URL=jdbc:postgresql://db:5432/keycloak \
  -e KC_DB_USERNAME=keycloak \
  -e KC_DB_PASSWORD=dbpassword \
  -e KC_HOSTNAME=auth.example.com \
  -e KC_HTTPS_CERTIFICATE_FILE=/certs/tls.crt \
  -e KC_HTTPS_CERTIFICATE_KEY_FILE=/certs/tls.key \
  -v /etc/ssl/keycloak:/certs:ro \
  quay.io/keycloak/keycloak:latest \
  start

Docker Compose with PostgreSQL

version: "3.9"

services:
  postgres:
    image: postgres:16
    container_name: keycloak-db
    environment:
      POSTGRES_DB: keycloak
      POSTGRES_USER: keycloak
      POSTGRES_PASSWORD: ${KC_DB_PASSWORD:-securepassword}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U keycloak"]
      interval: 10s
      timeout: 5s
      retries: 5

  keycloak:
    image: quay.io/keycloak/keycloak:latest
    container_name: keycloak
    environment:
      KC_DB: postgres
      KC_DB_URL: jdbc:postgresql://postgres:5432/keycloak
      KC_DB_USERNAME: keycloak
      KC_DB_PASSWORD: ${KC_DB_PASSWORD:-securepassword}
      KC_HOSTNAME: ${KC_HOSTNAME:-auth.example.com}
      KC_HOSTNAME_STRICT: "true"
      KC_HTTP_ENABLED: "true"
      KC_PROXY: edge
      KEYCLOAK_ADMIN: ${KC_ADMIN:-admin}
      KEYCLOAK_ADMIN_PASSWORD: ${KC_ADMIN_PASSWORD:-changeme}
    command: start --optimized
    ports:
      - "8080:8080"
    depends_on:
      postgres:
        condition: service_healthy
    restart: unless-stopped

volumes:
  postgres_data:

Kubernetes (Helm)

# Add Bitnami Helm repo
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update

# Install Keycloak with PostgreSQL
helm install keycloak bitnami/keycloak \
  --set auth.adminUser=admin \
  --set auth.adminPassword=securepassword \
  --set postgresql.enabled=true \
  --set postgresql.auth.password=dbpassword \
  --set ingress.enabled=true \
  --set ingress.hostname=auth.example.com \
  --namespace keycloak --create-namespace

# Check status
kubectl get pods -n keycloak
kubectl get svc -n keycloak

Standalone on Linux (from ZIP)

# Download and extract
curl -OL https://github.com/keycloak/keycloak/releases/download/24.0.0/keycloak-24.0.0.zip
unzip keycloak-24.0.0.zip
cd keycloak-24.0.0

# Configure database
echo 'db=postgres
db-url=jdbc:postgresql://localhost:5432/keycloak
db-username=keycloak
db-password=securepassword
hostname=auth.example.com' >> conf/keycloak.conf

# Start production server
export KEYCLOAK_ADMIN=admin
export KEYCLOAK_ADMIN_PASSWORD=changeme
bin/kc.sh build
bin/kc.sh start

Configuration

Key Environment Variables

# Core configuration
KC_DB=postgres                     # Database type: postgres, mysql, mariadb, mssql
KC_DB_URL=jdbc:postgresql://...    # JDBC URL
KC_DB_USERNAME=keycloak
KC_DB_PASSWORD=secret

# Hostname
KC_HOSTNAME=auth.example.com       # Public hostname
KC_HOSTNAME_STRICT=true            # Reject requests to other hostnames
KC_HTTP_ENABLED=true               # Allow HTTP (needed behind reverse proxy)
KC_PROXY=edge                      # Proxy mode: edge, reencrypt, passthrough

# TLS
KC_HTTPS_CERTIFICATE_FILE=/path/tls.crt
KC_HTTPS_CERTIFICATE_KEY_FILE=/path/tls.key

# Admin
KEYCLOAK_ADMIN=admin
KEYCLOAK_ADMIN_PASSWORD=changeme

# Performance
KC_HTTP_MAX_QUEUED_REQUESTS=1000
KC_CACHE=ispn                      # Infinispan clustering
KC_CACHE_STACK=kubernetes          # For K8s cluster discovery

Reverse Proxy (Nginx/Caddy) Setup

# Nginx config for Keycloak behind proxy
server {
    listen 443 ssl;
    server_name auth.example.com;

    location / {
        proxy_pass http://localhost:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_buffer_size 128k;
        proxy_buffers 4 256k;
    }
}
# Required Keycloak settings when behind a proxy
KC_PROXY=edge
KC_HOSTNAME=auth.example.com
KC_HTTP_ENABLED=true

Core Commands

CommandDescription
bin/kc.sh start-devStart in development mode (no TLS required)
bin/kc.sh startStart in production mode
bin/kc.sh start --optimizedStart with pre-built image (faster)
bin/kc.sh buildBuild optimized server image
bin/kc.sh export --dir /tmp/exportExport realm configuration to files
bin/kc.sh import --dir /tmp/exportImport realm configuration from files
bin/kc.sh show-configDisplay effective configuration
bin/kcadm.sh config credentials ...Authenticate kcadm CLI
bin/kcadm.sh get realmsList all realms
bin/kcadm.sh create realms -s realm=NAMECreate a new realm
bin/kcadm.sh get users -r REALMList users in a realm
bin/kcadm.sh create users -r REALMCreate a user
bin/kcadm.sh get clients -r REALMList clients in a realm
bin/kcadm.sh create clients -r REALMCreate a client
bin/kcadm.sh get groups -r REALMList groups
bin/kcadm.sh get roles -r REALMList roles
bin/kcadm.sh delete users/USER_ID -r REALMDelete a user

Advanced Usage

kcadm.sh CLI Administration

# Authenticate (get admin token)
bin/kcadm.sh config credentials \
  --server http://localhost:8080 \
  --realm master \
  --user admin \
  --password changeme

# Create a realm
bin/kcadm.sh create realms \
  -s realm=myrealm \
  -s enabled=true \
  -s displayName="My Application Realm"

# Create a user
bin/kcadm.sh create users -r myrealm \
  -s username=john.doe \
  -s email=john@example.com \
  -s firstName=John \
  -s lastName=Doe \
  -s enabled=true

# Set user password
bin/kcadm.sh set-password -r myrealm \
  --username john.doe \
  --new-password secretpassword \
  --temporary false

# Create a client (OIDC, confidential)
bin/kcadm.sh create clients -r myrealm \
  -s clientId=my-app \
  -s name="My Application" \
  -s enabled=true \
  -s clientAuthenticatorType=client-secret \
  -s 'redirectUris=["https://app.example.com/*"]' \
  -s 'webOrigins=["https://app.example.com"]' \
  -s protocol=openid-connect

# Get client secret
CLIENT_ID=$(bin/kcadm.sh get clients -r myrealm -q clientId=my-app | jq -r '.[0].id')
bin/kcadm.sh get clients/$CLIENT_ID/client-secret -r myrealm

# Create a role
bin/kcadm.sh create roles -r myrealm -s name=admin -s description="Admin role"

# Assign role to user
USER_ID=$(bin/kcadm.sh get users -r myrealm -q username=john.doe | jq -r '.[0].id')
ROLE_ID=$(bin/kcadm.sh get roles -r myrealm -q name=admin | jq -r '.[0].id')
bin/kcadm.sh add-roles -r myrealm --uusername john.doe --rolename admin

OIDC Token Flows via REST API

# Authorization Code flow — get auth URL
# Browser redirects user to:
# https://auth.example.com/realms/myrealm/protocol/openid-connect/auth
#   ?client_id=my-app
#   &redirect_uri=https://app.example.com/callback
#   &response_type=code
#   &scope=openid email profile

# Exchange authorization code for tokens
curl -X POST https://auth.example.com/realms/myrealm/protocol/openid-connect/token \
  -d "grant_type=authorization_code" \
  -d "client_id=my-app" \
  -d "client_secret=CLIENT_SECRET" \
  -d "code=AUTH_CODE" \
  -d "redirect_uri=https://app.example.com/callback"

# Client credentials flow (machine-to-machine)
curl -X POST https://auth.example.com/realms/myrealm/protocol/openid-connect/token \
  -d "grant_type=client_credentials" \
  -d "client_id=service-account" \
  -d "client_secret=SECRET"

# Refresh access token
curl -X POST https://auth.example.com/realms/myrealm/protocol/openid-connect/token \
  -d "grant_type=refresh_token" \
  -d "client_id=my-app" \
  -d "client_secret=SECRET" \
  -d "refresh_token=REFRESH_TOKEN"

# Introspect a token
curl -X POST https://auth.example.com/realms/myrealm/protocol/openid-connect/token/introspect \
  -u "my-app:SECRET" \
  -d "token=ACCESS_TOKEN"

# Get OIDC discovery document
curl https://auth.example.com/realms/myrealm/.well-known/openid-configuration

LDAP/Active Directory Federation

# Create LDAP user federation via API
bin/kcadm.sh create components -r myrealm \
  -s name="Corporate LDAP" \
  -s providerId=ldap \
  -s providerType=org.keycloak.storage.UserStorageProvider \
  -s 'config.vendor=["ad"]' \
  -s 'config.connectionUrl=["ldap://ad.corp.example.com:389"]' \
  -s 'config.bindDn=["CN=keycloak-svc,OU=ServiceAccounts,DC=corp,DC=example,DC=com"]' \
  -s 'config.bindCredential=["ldappassword"]' \
  -s 'config.usersDn=["OU=Users,DC=corp,DC=example,DC=com"]' \
  -s 'config.userObjectClasses=["person,organizationalPerson,user"]' \
  -s 'config.authType=["simple"]' \
  -s 'config.syncRegistrations=["false"]' \
  -s 'config.importEnabled=["true"]' \
  -s 'config.editMode=["READ_ONLY"]' \
  -s 'config.periodicFullSync=["true"]' \
  -s 'config.fullSyncPeriod=["604800"]' \
  -s 'config.periodicChangedUsersSync=["true"]' \
  -s 'config.changedSyncPeriod=["86400"]'

# Trigger manual sync
COMPONENT_ID="<component-id-from-above>"
bin/kcadm.sh create components/$COMPONENT_ID/sync?action=triggerFullSync -r myrealm

Multi-Factor Authentication Configuration

# Require OTP for a realm (via CLI)
bin/kcadm.sh update realms/myrealm \
  -s 'otpPolicyType=totp' \
  -s 'otpPolicyAlgorithm=HmacSHA1' \
  -s 'otpPolicyDigits=6' \
  -s 'otpPolicyPeriod=30'

# Require MFA via authentication flow
# Navigate in Admin UI: Authentication > Flows > Browser
# Set "OTP Form" execution to "Required"

# Require MFA for specific group/role via Conditional OTP
# Admin UI: Authentication > Flows > Create a copy of Browser flow
# Add "Condition - User Role" as required
# Configure which role triggers MFA requirement

Export and Import Realms

# Export specific realm (running server)
bin/kc.sh export \
  --dir /tmp/realm-export \
  --realm myrealm \
  --users realm_file

# Export all realms
bin/kc.sh export --dir /tmp/all-realms

# Import realm on startup
bin/kc.sh start-dev \
  --import-realm \
  --import-realm-file /tmp/realm-export/myrealm-realm.json

# Import via kcadm
bin/kcadm.sh create realms -f /tmp/realm-export/myrealm-realm.json

Custom Themes

# Theme directory structure
themes/
  my-theme/
    login/           # Login, registration, error pages
      theme.properties
      resources/
        css/
          custom.css
        img/
          logo.png
      messages/
        messages_en.properties
      login.ftl       # FreeMarker template override
    account/         # User account console
    email/           # Email templates

# theme.properties
parent=keycloak
import=common/keycloak

# Apply theme to realm
bin/kcadm.sh update realms/myrealm \
  -s loginTheme=my-theme \
  -s accountTheme=my-theme \
  -s emailTheme=my-theme

Common Workflows

New Application Onboarding

# 1. Create realm (or use existing)
bin/kcadm.sh create realms -s realm=myapp -s enabled=true

# 2. Create client
bin/kcadm.sh create clients -r myapp \
  -s clientId=my-web-app \
  -s enabled=true \
  -s protocol=openid-connect \
  -s publicClient=false \
  -s clientAuthenticatorType=client-secret \
  -s 'redirectUris=["https://app.example.com/*"]' \
  -s 'webOrigins=["+"]' \
  -s standardFlowEnabled=true \
  -s serviceAccountsEnabled=false

# 3. Get client secret
CLIENT_ID=$(bin/kcadm.sh get clients -r myapp -q clientId=my-web-app | jq -r '.[0].id')
bin/kcadm.sh get clients/$CLIENT_ID/client-secret -r myapp

# 4. Create roles
bin/kcadm.sh create roles -r myapp -s name=user
bin/kcadm.sh create roles -r myapp -s name=admin

# 5. Create test user
bin/kcadm.sh create users -r myapp -s username=testuser -s enabled=true
bin/kcadm.sh set-password -r myapp --username testuser --new-password test123
bin/kcadm.sh add-roles -r myapp --uusername testuser --rolename user

# 6. Test OIDC flow
curl -s -X POST \
  "http://localhost:8080/realms/myapp/protocol/openid-connect/token" \
  -d "grant_type=password&client_id=my-web-app&client_secret=SECRET&username=testuser&password=test123" \
  | jq .access_token

Health and Monitoring

# Health endpoints
curl http://localhost:8080/health
curl http://localhost:8080/health/ready
curl http://localhost:8080/health/live

# Metrics endpoint (Prometheus format)
curl http://localhost:8080/metrics

# Admin REST API — get realm stats
curl -H "Authorization: Bearer $TOKEN" \
  http://localhost:8080/admin/realms/myrealm/users/count

# Check active sessions
curl -H "Authorization: Bearer $TOKEN" \
  http://localhost:8080/admin/realms/myrealm/sessions/stats

Tips and Best Practices

PracticeDetails
Always use HTTPS in productionNever expose Keycloak on plain HTTP in production — TLS is mandatory
Use KC_PROXY=edgeRequired when running behind nginx/Caddy to pass real IP and proto headers
PostgreSQL for productionSQLite/H2 (default in dev) is not supported in production — use PostgreSQL
Export realm config to GitVersion-control realm JSON exports for disaster recovery and auditability
Tune token lifetimesSet short access token lifetimes (5-15 min) and longer refresh token lifetimes
Enable brute force protectionAdmin UI: Realm Settings > Security Defenses > Brute Force Detection
Use groups for role assignmentAssign roles to groups, add users to groups — easier than per-user role assignment
Dedicated service accountsCreate separate clients with service accounts for M2M auth, never reuse user clients
Health check all nodesMonitor /health/ready in load balancer health checks
Backup database regularlyRealm config, users, and sessions are all in the database
Client scope mappingUse protocol mappers to include custom claims in tokens
Review event logsEnable Admin Events and Login Events in Admin UI for audit trails