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
- 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
- Nginx Reverse Proxy with TLS
- Creating a Realm and First Client
- Integrating Applications via OIDC
- Connecting LDAP and Active Directory
- Enabling Multi-Factor Authentication
- Social Login: GitHub, Google, GitLab
- Groups, Roles, and Attribute Mapping
- Production Hardening and High Availability
- Troubleshooting Common Issues
- Conclusion
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
- 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
- Nginx Reverse Proxy with TLS
- Creating a Realm and First Client
- Integrating Applications via OIDC
- Connecting LDAP and Active Directory
- Enabling Multi-Factor Authentication
- Social Login: GitHub, Google, GitLab
- Groups, Roles, and Attribute Mapping
- Production Hardening and High Availability
- Troubleshooting Common Issues
- Conclusion
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
- Nginx Reverse Proxy with TLS
- Creating a Realm and First Client
- Integrating Applications via OIDC
- Connecting LDAP and Active Directory
- Enabling Multi-Factor Authentication
- Social Login: GitHub, Google, GitLab
- Groups, Roles, and Attribute Mapping
- Production Hardening and High Availability
- Troubleshooting Common Issues
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.,
companyrealm 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?
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.