Press ESC to close Press / to search

Keycloak on Linux: Self-Hosted SSO and Identity Provider Complete Setup Guide

🎯 Key Takeaways

  • Table of Contents
  • Why Self-Host Your Identity Provider
  • Keycloak Architecture: Realms, Clients, and Users
  • Deploy Keycloak with Docker Compose
  • Deploy Keycloak as a Native Service

πŸ“‘ Table of Contents

Keycloak is the leading open-source Identity and Access Management (IAM) platform, providing single sign-on (SSO), multi-factor authentication, social login, and fine-grained authorization for applications and services. Developed by Red Hat and now stewarded by the CNCF, Keycloak allows you to centralize authentication across all your internal tools β€” Grafana, Nextcloud, Forgejo, AWX, internal dashboards β€” so users log in once and access everything without re-entering credentials. This guide covers deploying Keycloak on Linux, configuring realms and clients, integrating LDAP, enabling MFA, and securing it for production.

Table of Contents

Why Self-Host Your Identity Provider

Every internal tool you deploy β€” monitoring dashboards, ticketing systems, wikis, CI/CD platforms β€” needs its own user database and login unless you centralize identity. Keycloak solves this by becoming the single authentication authority. Applications delegate login to Keycloak via OpenID Connect (OIDC) or SAML 2.0, and Keycloak handles the rest: user storage, password hashing, session management, MFA prompts, and brute-force detection. The practical benefits:

  • Single sign-on: Users authenticate once and access all connected applications without re-entering credentials.
  • Centralized MFA: Enable TOTP or WebAuthn once in Keycloak; every connected app inherits it automatically.
  • No vendor lock-in: OIDC and SAML are open standards. Every major app framework supports them.
  • Audit trail: Every login, failed attempt, and token issue is logged in one place.

Keycloak Architecture: Realms, Clients, and Users

Keycloak organizes everything around three core concepts:

  • Realm: An isolated namespace. The master realm is for Keycloak administration only. Create a separate realm for each environment or organization (e.g., company realm for all your internal apps).
  • Client: An application registered to use Keycloak for authentication. Each app (Grafana, Nextcloud, your custom API) is a Keycloak client with its own client ID and secret.
  • User: Can be stored locally in Keycloak’s built-in database, or federated from LDAP/Active Directory.

Authentication flow: user visits your app β†’ app redirects to Keycloak login page β†’ Keycloak authenticates the user β†’ issues a JWT token β†’ redirects back to the app with the token. The app validates the token using Keycloak’s public key.

docker-compose" id="install-docker">Deploy Keycloak with Docker Compose

mkdir -p /opt/keycloak && cd /opt/keycloak

cat > docker-compose.yml << 'COMPOSE'
services:
  keycloak:
    image: quay.io/keycloak/keycloak:26.1
    container_name: keycloak
    restart: unless-stopped
    command: start --optimized
    environment:
      KC_DB: postgres
      KC_DB_URL: jdbc:postgresql://keycloak-db:5432/keycloak
      KC_DB_USERNAME: keycloak
      KC_DB_PASSWORD: changeme_strong_password
      KC_HOSTNAME: sso.example.com
      KC_PROXY_HEADERS: xforwarded
      KC_HTTP_ENABLED: "true"
      KC_HTTPS_ENABLED: "false"
      KEYCLOAK_ADMIN: admin
      KEYCLOAK_ADMIN_PASSWORD: ChangeThisAdminPassword!
    ports:
      - "8080:8080"
    depends_on:
      keycloak-db:
        condition: service_healthy

  keycloak-db:
    image: postgres:16-alpine
    container_name: keycloak-db
    restart: unless-stopped
    environment:
      POSTGRES_DB: keycloak
      POSTGRES_USER: keycloak
      POSTGRES_PASSWORD: changeme_strong_password
    volumes:
      - ./postgres:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U keycloak"]
      interval: 10s
      timeout: 5s
      retries: 5
COMPOSE

docker compose up -d
docker compose logs -f keycloak

Keycloak starts in production mode (start). The admin console is available at http://localhost:8080 during initial setup β€” once nginx is in place, all traffic goes through HTTPS.

Deploy Keycloak as a Native Service

# Download Keycloak distribution
KEYCLOAK_VERSION=26.1.0
wget "https://github.com/keycloak/keycloak/releases/download/${KEYCLOAK_VERSION}/keycloak-${KEYCLOAK_VERSION}.tar.gz"
tar -xzf keycloak-${KEYCLOAK_VERSION}.tar.gz -C /opt/
ln -s /opt/keycloak-${KEYCLOAK_VERSION} /opt/keycloak

# Create system user
useradd --system --shell /sbin/nologin --home-dir /opt/keycloak keycloak
chown -R keycloak:keycloak /opt/keycloak

# Create environment configuration
cat > /etc/keycloak.conf << 'CONF'
db=postgres
db-url=jdbc:postgresql://localhost:5432/keycloak
db-username=keycloak
db-password=strong_password_here
hostname=sso.example.com
proxy-headers=xforwarded
http-enabled=true
CONF
chmod 600 /etc/keycloak.conf

# Build optimized startup artifacts (run once after install or config change)
sudo -u keycloak /opt/keycloak/bin/kc.sh build

# Create systemd service
cat > /etc/systemd/system/keycloak.service << 'UNIT'
[Unit]
Description=Keycloak Identity Provider
After=network-online.target postgresql.service
Wants=network-online.target

[Service]
Type=idle
User=keycloak
Group=keycloak
ExecStart=/opt/keycloak/bin/kc.sh start --optimized
EnvironmentFile=/etc/keycloak.conf
Environment=KEYCLOAK_ADMIN=admin
Environment=KEYCLOAK_ADMIN_PASSWORD=ChangeThisOnFirstBoot!
Restart=on-failure
RestartSec=10s

[Install]
WantedBy=multi-user.target
UNIT

systemctl daemon-reload
systemctl enable --now keycloak

Nginx Reverse Proxy with TLS

certbot certonly --standalone -d sso.example.com \
  --email admin@example.com --agree-tos -n

cat > /etc/nginx/sites-available/keycloak << 'NGINX'
server {
    listen 80;
    server_name sso.example.com;
    return 301 https://$host$request_uri;
}

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

    ssl_certificate     /etc/letsencrypt/live/sso.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/sso.example.com/privkey.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;

    client_max_body_size 10m;

    location / {
        proxy_pass         http://127.0.0.1:8080;
        proxy_http_version 1.1;
        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_set_header   X-Forwarded-Port  $server_port;
        proxy_buffer_size  128k;
        proxy_buffers      4 256k;
    }
}
NGINX

ln -s /etc/nginx/sites-available/keycloak /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx

Creating a Realm and First Client

Log in to https://sso.example.com with your admin credentials. The master realm is for Keycloak admin only β€” create a dedicated realm for your organization.

# Using the Keycloak CLI (kcadm.sh) β€” useful for automation
/opt/keycloak/bin/kcadm.sh config credentials \
  --server https://sso.example.com \
  --realm master \
  --user admin \
  --password ChangeThisAdminPassword!

# Create a realm
/opt/keycloak/bin/kcadm.sh create realms \
  -s realm=company \
  -s enabled=true \
  -s displayName="Company SSO" \
  -s registrationAllowed=false \
  -s bruteForceProtected=true

# Create a client (e.g., for Grafana)
/opt/keycloak/bin/kcadm.sh create clients -r company \
  -s clientId=grafana \
  -s name="Grafana Dashboard" \
  -s enabled=true \
  -s publicClient=false \
  -s standardFlowEnabled=true \
  -s 'redirectUris=["https://grafana.example.com/login/generic_oauth"]' \
  -s 'webOrigins=["https://grafana.example.com"]'

Integrating Applications via OIDC

Most modern applications support OIDC natively. The key values needed from Keycloak (found at https://sso.example.com/realms/company/.well-known/openid-configuration):

  • Authorization URL: https://sso.example.com/realms/company/protocol/openid-connect/auth
  • Token URL: https://sso.example.com/realms/company/protocol/openid-connect/token
  • JWKS URL: https://sso.example.com/realms/company/protocol/openid-connect/certs
# Example: Grafana OIDC configuration in grafana.ini
[auth.generic_oauth]
enabled = true
name = Keycloak
allow_sign_up = true
client_id = grafana
client_secret = 
scopes = openid email profile roles
auth_url = https://sso.example.com/realms/company/protocol/openid-connect/auth
token_url = https://sso.example.com/realms/company/protocol/openid-connect/token
api_url = https://sso.example.com/realms/company/protocol/openid-connect/userinfo
role_attribute_path = contains(roles[*], 'grafana-admin') && 'Admin' || 'Viewer'

Connecting LDAP and Active Directory

# Configure LDAP User Federation via Keycloak admin console:
# Realm β†’ User Federation β†’ Add LDAP provider

/opt/keycloak/bin/kcadm.sh create components -r company \
  -s name="Company LDAP" \
  -s providerId=ldap \
  -s providerType=org.keycloak.storage.UserStorageProvider \
  -s 'config.connectionUrl=["ldap://ldap.example.com:389"]' \
  -s 'config.bindDn=["cn=keycloak,ou=service-accounts,dc=example,dc=com"]' \
  -s 'config.bindCredential=["ldap_bind_password"]' \
  -s 'config.usersDn=["ou=users,dc=example,dc=com"]' \
  -s 'config.usernameLDAPAttribute=["sAMAccountName"]' \
  -s 'config.rdnLDAPAttribute=["cn"]' \
  -s 'config.uuidLDAPAttribute=["objectGUID"]' \
  -s 'config.userObjectClasses=["person, organizationalPerson, user"]' \
  -s 'config.authType=["simple"]' \
  -s 'config.syncRegistrations=["false"]' \
  -s 'config.editMode=["READ_ONLY"]'

# For Active Directory, use the AD-specific provider:
# User Object Classes: person, organizationalPerson, user
# Username LDAP Attribute: sAMAccountName (or userPrincipalName)

Enabling Multi-Factor Authentication

# Enable TOTP as a required action for all users in a realm
# Realm Settings β†’ Authentication β†’ Required Actions β†’ OTP β†’ Default On

# Or via CLI β€” make OTP required for all new logins:
/opt/keycloak/bin/kcadm.sh update authentication/required-actions/CONFIGURE_TOTP \
  -r company \
  -s enabled=true \
  -s defaultAction=true

# WebAuthn (FIDO2/hardware keys) β€” enable the WebAuthn authenticator:
# Authentication β†’ Flows β†’ Browser β†’ add WebAuthn Authenticator

# Per-user MFA: Users β†’ select user β†’ Required User Actions β†’ Configure OTP
# User receives a QR code on next login to register their authenticator app

Social Login: GitHub, Google, GitLab

# Add GitHub as an identity provider
# 1. Create a GitHub OAuth App: github.com β†’ Settings β†’ Developer settings
#    Homepage URL: https://sso.example.com
#    Callback URL: https://sso.example.com/realms/company/broker/github/endpoint

# 2. Add provider in Keycloak (Identity Providers β†’ GitHub)
/opt/keycloak/bin/kcadm.sh create identity-provider/instances -r company \
  -s alias=github \
  -s providerId=github \
  -s enabled=true \
  -s 'config.clientId=your_github_client_id' \
  -s 'config.clientSecret=your_github_client_secret' \
  -s 'config.defaultScope=user:email'

# Google SSO
# Callback URL: https://sso.example.com/realms/company/broker/google/endpoint
/opt/keycloak/bin/kcadm.sh create identity-provider/instances -r company \
  -s alias=google \
  -s providerId=google \
  -s enabled=true \
  -s 'config.clientId=your_google_client_id' \
  -s 'config.clientSecret=your_google_client_secret'

Groups, Roles, and Attribute Mapping

# Create realm roles
/opt/keycloak/bin/kcadm.sh create roles -r company \
  -s name=sysadmin \
  -s description="Full system administration access"

/opt/keycloak/bin/kcadm.sh create roles -r company \
  -s name=developer \
  -s description="Developer access to CI/CD and monitoring"

# Create a group and assign a role to it
/opt/keycloak/bin/kcadm.sh create groups -r company -s name=ops-team
GROUP_ID=$(kcadm.sh get groups -r company | jq -r '.[] | select(.name=="ops-team") | .id')
/opt/keycloak/bin/kcadm.sh add-roles -r company \
  --gname ops-team --rolename sysadmin

# Add a user to a group
/opt/keycloak/bin/kcadm.sh update users//groups/${GROUP_ID} -r company

# Map LDAP groups to Keycloak groups automatically:
# User Federation β†’ your LDAP β†’ Mappers β†’ Add group-ldap-mapper
# LDAP Groups DN: ou=groups,dc=example,dc=com
# Membership LDAP Attribute: member

Production Hardening and High Availability

# Disable master realm admin console from public internet
# Add this to nginx: deny access to /auth/admin/master for non-internal IPs
# location /realms/master/protocol/ { deny all; }

# Keycloak cluster (active-active HA) β€” run 2+ instances sharing the same DB
# Add to keycloak.conf on each node:
# cache=ispn
# cache-stack=kubernetes  (or tcp for non-k8s)

# Key production settings in keycloak.conf:
cat >> /etc/keycloak.conf << 'CONF'
# Session and token timeouts (defaults are too long)
# Configure per-realm in: Realm Settings β†’ Tokens
# Access Token Lifespan: 5 minutes
# SSO Session Idle: 30 minutes
# SSO Session Max: 8 hours
CONF

# Backup Keycloak database regularly
pg_dump -U keycloak keycloak | gzip > /backups/keycloak-$(date +%Y%m%d).sql.gz

# Export realm configuration (for migration/backup)
/opt/keycloak/bin/kc.sh export \
  --dir /backups/keycloak-realm-export \
  --realm company

Troubleshooting Common Issues

# Keycloak not starting β€” check logs
docker compose logs keycloak --tail=50
# or for native:
journalctl -u keycloak -f

# "Invalid redirect_uri" error when logging into an app
# β†’ The redirect URI in the app config must exactly match what's registered
#   in the Keycloak client's Valid Redirect URIs (including trailing slashes)

# Users can't log in after LDAP sync
# Test LDAP connection: User Federation β†’ your provider β†’ Action β†’ Synchronize all users
# Check bind credentials: Action β†’ Test connection, then Test authentication

# Token validation failing
# Verify the app is using the correct realm's JWKS endpoint
# Check token expiry: Access Token Lifespan in Realm Settings β†’ Tokens

# Admin console locked out
# Reset admin password via CLI:
/opt/keycloak/bin/kcadm.sh set-password -r master \
  --username admin --new-password NewPassword123!

# High memory usage
# Keycloak defaults to 512MB heap; increase for production:
# Add to systemd service: Environment=JAVA_OPTS="-Xms1g -Xmx2g"

Conclusion

Keycloak transforms a fragmented collection of per-application logins into a unified identity layer across your entire infrastructure. Once connected, every application delegates authentication to Keycloak β€” meaning one password policy, one MFA requirement, one audit log, and one logout button that ends all sessions everywhere. The initial setup is the steepest part; after that, adding a new application is a ten-minute exercise of registering a client, pasting four URLs into the application's config, and enabling SSO. For organizations running more than three internal tools, centralized identity management with Keycloak pays for its operational overhead within the first month.

Was this article helpful?

Advertisement
🏷️ Tags: active directory authentication identity provider keycloak LDAP MFA OIDC OpenID Connect SAML self-hosted single sign-on SSO
R

About Ramesh Sundararamaiah

Red Hat Certified Architect

Expert in Linux system administration, DevOps automation, and cloud infrastructure. Specializing in Red Hat Enterprise Linux, CentOS, Ubuntu, Docker, Ansible, and enterprise IT solutions.

🐧 Stay Updated with Linux Tips

Get the latest tutorials, news, and guides delivered to your inbox weekly.

Advertisement

Add Comment


↑