Skip to main content

Self-Hosting Keycloak for Authentication 2026

·OSSAlt Team
keycloakauthenticationself-hostingdockerguide
Share:

Keycloak is the most battle-tested open source identity platform. It replaces Auth0, Okta, and Azure AD — providing SSO, OIDC, SAML, LDAP, and social login for all your applications.

Requirements

  • VPS with 2 GB RAM minimum (4 GB recommended)
  • Docker and Docker Compose
  • Domain name (e.g., auth.yourdomain.com)
  • 10+ GB disk

Step 1: Create Docker Compose

# docker-compose.yml
services:
  keycloak:
    image: quay.io/keycloak/keycloak:latest
    container_name: keycloak
    restart: unless-stopped
    ports:
      - "8080:8080"
    environment:
      - KC_DB=postgres
      - KC_DB_URL_HOST=db
      - KC_DB_URL_DATABASE=keycloak
      - KC_DB_USERNAME=keycloak
      - KC_DB_PASSWORD=your-strong-password
      - KC_HOSTNAME=auth.yourdomain.com
      - KC_PROXY_HEADERS=xforwarded
      - KC_HTTP_ENABLED=true
      - KEYCLOAK_ADMIN=admin
      - KEYCLOAK_ADMIN_PASSWORD=your-admin-password
    command: start
    depends_on:
      - db

  db:
    image: postgres:16-alpine
    container_name: keycloak-db
    restart: unless-stopped
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=keycloak
      - POSTGRES_USER=keycloak
      - POSTGRES_PASSWORD=your-strong-password

volumes:
  postgres_data:

Step 2: Start Keycloak

docker compose up -d

Step 3: Reverse Proxy (Caddy)

# /etc/caddy/Caddyfile
auth.yourdomain.com {
    reverse_proxy localhost:8080
}
sudo systemctl restart caddy

Step 4: Initial Configuration

  1. Open https://auth.yourdomain.com
  2. Login with admin credentials
  3. Create a new realm (don't use the master realm for applications)

Recommended realm setup:

  1. Click Create Realm
  2. Name it (e.g., mycompany)
  3. Enable it

Step 5: Configure a Client (Your Application)

  1. Go to your realm → ClientsCreate client
  2. Client type: OpenID Connect
  3. Client ID: my-app
  4. Client authentication: On (for server-side apps) / Off (for SPAs)
  5. Valid redirect URIs: https://myapp.com/callback
  6. Web origins: https://myapp.com

Save and note the client secret from the Credentials tab.

Step 6: Add Social Login Providers

Go to Identity Providers:

ProviderConfiguration
GoogleClient ID + Secret from Google Cloud Console
GitHubClient ID + Secret from GitHub OAuth Apps
MicrosoftClient ID + Secret from Azure App Registration
AppleService ID + Key from Apple Developer Portal

Step 7: Integrate with Your App

Next.js (with NextAuth.js):

// app/api/auth/[...nextauth]/route.js
import NextAuth from 'next-auth'
import KeycloakProvider from 'next-auth/providers/keycloak'

export const authOptions = {
  providers: [
    KeycloakProvider({
      clientId: 'my-app',
      clientSecret: 'your-client-secret',
      issuer: 'https://auth.yourdomain.com/realms/mycompany',
    }),
  ],
}

const handler = NextAuth(authOptions)
export { handler as GET, handler as POST }

React SPA (with keycloak-js):

import Keycloak from 'keycloak-js'

const keycloak = new Keycloak({
  url: 'https://auth.yourdomain.com',
  realm: 'mycompany',
  clientId: 'my-spa',
})

await keycloak.init({ onLoad: 'login-required' })
console.log('Authenticated:', keycloak.authenticated)
console.log('Token:', keycloak.token)

Any OIDC-compatible app:

Issuer URL:    https://auth.yourdomain.com/realms/mycompany
Auth URL:      https://auth.yourdomain.com/realms/mycompany/protocol/openid-connect/auth
Token URL:     https://auth.yourdomain.com/realms/mycompany/protocol/openid-connect/token
Userinfo URL:  https://auth.yourdomain.com/realms/mycompany/protocol/openid-connect/userinfo

Step 8: User Management

Create users manually:

  1. UsersAdd user
  2. Set username, email, first/last name
  3. Credentials tab → set password

LDAP integration:

  1. User FederationAdd LDAP provider
  2. Configure connection URL, bind DN, user DN
  3. Sync users on demand or schedule

Self-registration:

  1. Realm SettingsLogin tab
  2. Enable User registration

Step 9: Configure Email

Go to Realm SettingsEmail:

SettingValue
Hostsmtp.resend.com
Port587
Fromauth@yourdomain.com
Enable StartTLS
Usernameresend
Passwordre_your_api_key

Required for password reset, email verification, and account notifications.

Step 10: Security Settings

Brute force protection:

  • Realm SettingsSecurity Defenses
  • Enable brute force detection
  • Set max login failures (e.g., 5)
  • Set lockout duration (e.g., 15 minutes)

Password policies:

  • AuthenticationPoliciesPassword Policy
  • Add: length(8), digits(1), upperCase(1), specialChars(1)

MFA/2FA:

  • AuthenticationFlows
  • Set OTP as required or conditional for browser flow

Production Hardening

Performance tuning:

environment:
  - KC_CACHE=ispn
  - KC_CACHE_STACK=kubernetes  # For clustered deployments
  - JAVA_OPTS_KC_HEAP=-Xms512m -Xmx1024m

Backups:

# Database backup (daily cron)
docker exec keycloak-db pg_dump -U keycloak keycloak > /backups/keycloak-$(date +%Y%m%d).sql

# Export realm configuration
docker exec keycloak /opt/keycloak/bin/kc.sh export \
  --dir /opt/keycloak/data/export \
  --realm mycompany
docker cp keycloak:/opt/keycloak/data/export /backups/keycloak-realm-$(date +%Y%m%d)

Updates:

docker compose pull
docker compose up -d

Monitoring:

  • Enable health endpoint: KC_HEALTH_ENABLED=true
  • Monitor /health/ready and /health/live
  • Track login failures and token issuance rates

Resource Usage

UsersRAMCPUDisk
1-5002 GB2 cores10 GB
500-5K4 GB4 cores20 GB
5K-50K8 GB8 cores50 GB

VPS Recommendations

ProviderSpec (1K users)Price
Hetzner4 vCPU, 8 GB RAM€8/month
DigitalOcean2 vCPU, 4 GB RAM$24/month
Linode2 vCPU, 4 GB RAM$24/month

Why Self-Host Keycloak

Auth0 is the dominant managed identity provider, and their pricing reflects that market position. The Free plan covers 7,500 active users, but once you exceed that or need enterprise features (organizations, custom domains, MFA enforcement), you move to the Professional plan starting at $240/month. For a B2B SaaS with hundreds of business customers, Auth0 bills can reach $500–2,000/month. Okta and Azure AD B2C scale similarly — Okta's Workforce Identity starts at $2/user/month, and customer-facing identity costs stack quickly for growing user bases.

Self-hosted Keycloak handles all of these use cases — SSO, SAML federation, social login, LDAP integration, TOTP, and FIDO2 — at no licensing cost. Your only expense is the server: a Hetzner CX21 at €3.79/month handles up to 5,000 users without breaking a sweat. Even with 4x that capacity (€8/month), the savings against managed IdPs are substantial within the first quarter.

Identity consolidation: One of Keycloak's most powerful features is acting as a broker between your applications and multiple identity providers. Rather than configuring Google OAuth separately in every application, you configure it once in Keycloak and all your apps inherit the integration. The same applies to LDAP/Active Directory federation, SAML providers, and social logins.

Compliance and data ownership: Authentication events — login times, IP addresses, failed attempts, session durations — are sensitive operational data. Keeping this data on your own infrastructure simplifies GDPR compliance documentation and gives you full audit log access without requesting exports from a vendor.

When NOT to self-host Keycloak: Keycloak is a Java application with a non-trivial memory footprint and a complex administration interface. If your authentication needs are simple (one app, password login, no SSO requirements), Auth0's free tier or a simpler solution like Authentik may be easier to operate. Keycloak's power comes with operational complexity — plan for time investment in initial configuration and ongoing maintenance, particularly around upgrades, which occasionally include breaking changes.

Prerequisites (Expanded)

VPS with 2 GB RAM minimum (4 GB recommended): Keycloak is a Java application built on Quarkus, and the JVM has a real memory floor. With 2 GB RAM, you can run Keycloak and PostgreSQL on the same host — but it's snug. The JVM heap is set to 512 MB by default (JAVA_OPTS_KC_HEAP=-Xms512m -Xmx1024m), leaving room for the OS and PostgreSQL. For production with 500+ active users or multiple realms, 4 GB is the comfortable starting point.

Domain name: Keycloak's authentication flows depend on consistent URLs. OIDC discovery documents, redirect URIs registered in clients, and SAML metadata all reference your domain. Changing the domain later requires reconfiguring every client and updating every application's OIDC configuration. Choose your subdomain (e.g., auth.yourdomain.com) and plan to keep it stable.

10+ GB disk: PostgreSQL stores session data, user attributes, and event logs. For small deployments (under 1,000 users), 10 GB is ample. For larger deployments with event logging enabled, monitor disk usage and consider archiving old events. Keycloak's admin console includes an Events configuration where you can set retention periods.

Skill level: Keycloak has a significant learning curve. Realm configuration, client protocols, authentication flows, and federation adapters all require understanding identity concepts like OAuth2 grant types, SAML assertions, and JWT claims. Budget time for learning the administration interface before deploying in production. The official documentation is comprehensive, though dense.

For VPS selection, Hetzner's CX31 (€6.49/month, 2 vCPU, 4 GB RAM) hits the sweet spot for single-server Keycloak deployments. See the VPS comparison for self-hosters for a full breakdown of provider options including network performance and data center locations.

Production Security Hardening

Authentication infrastructure is the highest-value target in your stack. A compromised Keycloak instance means an attacker controls identity for every application federated through it.

Firewall with UFW: Keycloak runs on port 8080 internally; your reverse proxy exposes it via HTTPS. Port 8080 must never be directly reachable from the internet.

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable

PostgreSQL (5432) should similarly never be exposed publicly.

Fail2ban for SSH and Keycloak: Install fail2ban to automatically ban IPs with repeated login failures.

sudo apt install fail2ban

Keycloak has built-in brute force protection (configured in Realm Settings → Security Defenses), which is separate from and complementary to fail2ban. Enable both — Keycloak's protection handles per-account lockouts; fail2ban handles IP-level blocking.

Keep secrets in .env files: Never hardcode KEYCLOAK_ADMIN_PASSWORD or KC_DB_PASSWORD directly in docker-compose.yml. Store credentials in a .env file excluded from version control. In production, consider using Docker secrets or a proper secrets manager (Vault, AWS Secrets Manager) for the database password and admin credentials.

Disable SSH password authentication:

sudo sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
sudo systemctl restart sshd

Use a dedicated PostgreSQL user: The keycloak database user in the Compose configuration has full access to the Keycloak database. This follows least-privilege principles — Keycloak doesn't need (and shouldn't have) access to other databases on the same server.

Enable automatic security updates:

sudo apt install unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgrades

For a complete server security checklist, including TLS configuration, log monitoring, and certificate management for self-hosted authentication infrastructure, see the self-hosting security checklist.

Troubleshooting Common Issues

Keycloak fails to start — "DB_ADDR is not set"

In Keycloak 20+, the environment variable names changed from the legacy WildFly-based format. If you're using an older guide, DB_ADDR is now KC_DB_URL_HOST and DB_PASSWORD is now KC_DB_PASSWORD. Verify that your Compose file uses the current Quarkus-based variable names as shown above.

docker compose logs keycloak | head -50

"Hostname not configured" error in the browser

Keycloak requires that KC_HOSTNAME exactly matches the hostname your users access. If you're seeing this error, verify that KC_HOSTNAME=auth.yourdomain.com in your environment matches the domain your reverse proxy is serving. Mismatches between the proxy hostname and KC_HOSTNAME cause this error.

OIDC redirect fails — "invalid_redirect_uri"

The redirect URI registered in your Keycloak client must exactly match the callback URL your application sends. Common mistakes: trailing slashes (/callback vs /callback/), http vs https, and localhost in production configs. Go to your client's Valid redirect URIs and add the exact URL your application sends.

Keycloak is very slow on first request after idle

The JVM goes to sleep when idle to conserve memory. The first request after a period of inactivity triggers JVM warmup, causing a noticeable delay. This is expected behavior. For production deployments where consistent response times matter, add a health check cron job that hits the Keycloak health endpoint every few minutes to keep the JVM warm:

*/5 * * * * curl -sf https://auth.yourdomain.com/health/ready > /dev/null

"Too many connections" error from PostgreSQL

Keycloak opens a connection pool to PostgreSQL on startup. If you're running multiple Keycloak replicas or have other services sharing the same PostgreSQL instance, you may exhaust the connection limit. Increase max_connections in PostgreSQL or add PgBouncer as a connection pooler in front of the database.

Users can't log in after upgrading Keycloak

Keycloak upgrades occasionally include database schema migrations that run automatically on first boot. If the migration fails or is interrupted, the database may be in an inconsistent state. Always take a PostgreSQL backup before upgrading:

docker exec keycloak-db pg_dump -U keycloak keycloak > /backups/keycloak-pre-upgrade.sql

If a migration fails, restore from this backup and investigate the upgrade changelog for manual migration steps.

Keycloak vs Authentik vs Authelia

For self-hosters evaluating identity provider options, Keycloak, Authentik, and Authelia serve different points in the complexity/feature spectrum.

Keycloak is the enterprise-grade choice. It supports the full OIDC, SAML 2.0, and LDAP feature sets, handles complex realm configurations with fine-grained authorization policies, and has been battle-tested in large enterprise environments. The learning curve is real — concepts like realms, clients, and authorization services require investment to understand. But for organizations that need SAML federation with existing enterprise identity providers, or require fine-grained authorization policies across multiple applications, Keycloak's depth is necessary.

Authentik is a modern alternative that covers the core OIDC and SAML use cases with a cleaner UI and better documentation for self-hosters. For teams primarily concerned with SSO for internal services (Gitea, Nextcloud, Grafana, Outline), Authentik often provides a better experience with less configuration overhead. The Python/Django stack is easier to customize than Keycloak's Java internals.

Authelia is the lightest option — a forward authentication middleware that adds two-factor authentication and SSO in front of your services via Caddy or Nginx. It doesn't run a full identity provider but handles authentication flow for proxied services. For simple setups without SAML requirements, Authelia is dramatically easier to configure than either Keycloak or Authentik.

See the authentik vs Keycloak vs Authelia comparison for a detailed feature and use-case breakdown, and the best open source authentication solutions for the full landscape of identity management tools.

Set up automated server backups with restic to protect your Keycloak PostgreSQL database.

Compare authentication platforms on OSSAlt — protocols, features, and pricing side by side.

See open source alternatives to Keycloak on OSSAlt.

The SaaS-to-Self-Hosted Migration Guide (Free PDF)

Step-by-step: infrastructure setup, data migration, backups, and security for 15+ common SaaS replacements. Used by 300+ developers.

Join 300+ self-hosters. Unsubscribe in one click.