Skip to main contentSkip to navigation
[email protected]
Client AreaSupport
Hosting Mammoth
HostingMammothYour Data, Our Responsibility
Home
Solutions
Hosting Services
Store
Pricing
About
Blog
API
Contact

Stay Ahead of the Curve

Get the latest insights on cybersecurity, AI innovations, and enterprise data solutions delivered to your inbox.

Hosting Mammoth
HostingMammothEnterprise Solutions

Enterprise-grade data solutions. Hosting, recovery, cybersecurity, and AI-powered services for businesses worldwide.

[email protected]
Sun - Fri, 9:00am - 5:00pm

Services

  • Cloud Hosting
  • Data Recovery
  • Cybersecurity
  • Legal Support
  • MSP Services
  • Web Development
  • AI Services
  • Free Server Migration

Hosting

  • VPS Hosting (NVMe SSD)
  • VDS Hosting (NVMe)
  • Storage VPS (High SSD)
  • GPU Servers
  • Managed Services
  • Cloud Firewall
  • Load Balancer
  • One-Click Apps
  • n8n Hosting
  • Object Storage
  • FAQ

Company

  • Store
  • Pricing
  • About Us
  • Locations
  • Blog
  • Testimonials
  • Contact
  • Affiliate Program
  • White-Label
  • Terms of Service
  • Privacy Policy
  • Browser Cookies
  • SLA

Support

  • Client Area
  • Submit Ticket
  • Knowledge Base
  • Server Status
  • API Documentation

© 2026 Hosting Mammoth. All rights reserved.

Knowledge Base
Getting StartedAccount ManagementVPS HostingGPU ServersStorage VPSCloud FirewallLoad BalancerServer ManagementBilling & PaymentsSupport & TicketsAffiliate ProgramReseller ProgramMarketplace & Appsn8n HostingManaged ServicesServer MigrationAPI & DevelopersSecurityTroubleshootingGlossaryInstall Guides
  1. Home
  2. /
  3. Support
  4. /
  5. Install Guides
  6. /
  7. How To Install Keycloak Ubuntu
GUIDEInstall Guides

How to Install Keycloak on Ubuntu 24.04 VPS: Enterprise-Grade Open-Source IAM

30 min read

How to Install Keycloak on Ubuntu 24.04 VPS: Enterprise-Grade Open-Source IAM

Identity and Access Management (IAM) is the beating heart of any modern application stack. Every app you deploy needs login, password reset, MFA, session management, social login, and some form of role-based access control. Building that yourself is expensive and risky. Outsourcing it to Auth0, Okta, or Azure AD B2C is fast, but the bill grows fast too — and every user authenticates against someone else's infrastructure.

Keycloak is the open-source alternative: a battle-tested, enterprise-grade IAM platform maintained by Red Hat, used by banks, government agencies, and Fortune 500s. Running it on your own VPS gives you unlimited users, unlimited realms, OIDC + SAML + LDAP federation, social login, MFA, fine-grained authorization, and full control over your authentication data — for the flat cost of a single server.

In this tutorial you will install Keycloak 25+ (the Quarkus distribution) on Ubuntu 24.04 with a PostgreSQL backend, a production systemd service, and TLS via an Nginx reverse proxy. You will then bootstrap an admin account, create a realm, register OIDC and SAML clients, wire up Google and GitHub social login, federate an LDAP directory, apply a custom theme, and set up scheduled pg_dump backups and a tested upgrade path.

Recommended Plan. Keycloak needs real memory for the JVM and PostgreSQL — the CloudCore Professional VPS (6 vCPU / 12 GB RAM / 100 GB NVMe) is the sweet spot for up to ~50,000 users and comfortable dev/staging room.

Table of Contents

  • What Is Keycloak and Why Self-Host It?
  • Self-Hosted Keycloak vs Auth0/Okta: Cost Reality
  • Prerequisites
  • Step 1: Update the System and Create a Service User
  • Step 2: Install JDK 21
  • Step 3: Install and Configure PostgreSQL
  • Step 4: Download the Keycloak 25 Quarkus Distribution
  • Step 5: Configure Keycloak (keycloak.conf)
  • Step 6: Build and Bootstrap the Admin
  • Step 7: Create a systemd Service
  • Step 8: TLS via Nginx Reverse Proxy
  • Step 9: Create Your First Realm
  • Step 10: Register an OIDC Client (Grafana + Custom App)
  • Step 11: Register a SAML Client
  • Step 12: Identity Providers — Google, GitHub, LDAP
  • Step 13: Custom Themes
  • Step 14: Events, Audit, and Monitoring
  • Step 15: Backups with pg_dump
  • Step 16: Upgrading Keycloak
  • Troubleshooting
  • FAQ
  • Next Steps
  • What Is Keycloak and Why Self-Host It?

    Keycloak is an open-source identity and access management server originally built by JBoss/Red Hat. It speaks three of the industry's standard authentication protocols fluently:

    • OpenID Connect (OIDC) — the modern OAuth 2.0 identity layer used by almost every SaaS and SPA.
    • OAuth 2.0 — for delegated authorization and API access tokens.
    • SAML 2.0 — still dominant in enterprise and education (Shibboleth, Salesforce, AWS IAM, Atlassian).
    On top of the protocols, Keycloak provides features that would take years to build yourself: user registration, password policies, email verification, WebAuthn/FIDO2 MFA, TOTP MFA, account recovery, session/offline-token management, brute-force detection, fine-grained authorization services (UMA 2.0), identity brokering (delegating login to Google/GitHub/Microsoft), LDAP and Active Directory federation, per-realm themes, scriptable authentication flows, admin REST API, and a slick self-service Account Console for end users.

    The distribution shipped since Keycloak 20 is built on Quarkus — a Java framework that starts in seconds, has a small memory footprint, and supports native/container builds. It replaced the old Wildfly distribution and is what you will install here.

    Typical Self-Hosted Keycloak Use Cases

    • Single Sign-On (SSO) across internal tools — log in once, access Grafana, Gitea, Jenkins, Nextcloud, Mattermost, Harbor, and Argo CD with the same account.
    • B2B/B2C product authentication — give your SaaS real login, social login, MFA, and self-service without paying per-MAU.
    • Replacing Auth0/Okta/Azure AD B2C — same OIDC contract, your infrastructure, no per-user bill.
    • Federating legacy LDAP/AD — modernize an on-prem Active Directory with OIDC/SAML on top without touching the directory.
    • Customer portals with social login — add Sign in with Google/GitHub/Apple/Microsoft in minutes via Keycloak Identity Providers.

    Self-Hosted Keycloak vs Auth0/Okta: Cost Reality

    IAM SaaS prices escalate quickly once you pass the free tier. Here is what the same workload costs on the major platforms versus a CloudCore Professional VPS running Keycloak:

    Users / ScenarioAuth0 (Essentials → B2B)Okta (Customer Identity)Azure AD B2CSelf-hosted Keycloak (CloudCore Professional)
    1,000 monthly active users (MAU)~$35-$240/mo~$150+/mo~$0-$6/moEUR 19.99/mo flat
    10,000 MAU, social login + MFA~$1,200+/mo~$1,500+/mo~$55/mo + add-onsEUR 19.99/mo flat
    50,000 MAU, MFA, org mgmt, custom domain~$3,000+/mo (Enterprise)Custom enterprise quote~$275+/mo + per-auth feesEUR 19.99/mo flat
    Branded/custom domainPaid add-onPaid add-onPaidFree
    Unlimited realms / tenantsPaid tierPaid tierComplexFree
    Data residency / complianceLimited regionsLimited regionsMicrosoft regionsYour VPS, your jurisdiction
    A single Keycloak instance on a 12 GB VPS can comfortably serve tens of thousands of MAU. Beyond the raw cost savings, self-hosting gives you: no per-MAU surprise bills, complete data residency control (important for GDPR, HIPAA, and financial regulators), unlimited environments (dev/staging/prod realms), the ability to write custom authenticators, themes, and SPIs in Java, and no vendor lock-in — your user database is public.user_entity in a standard PostgreSQL you own.

    Prerequisites

    Before you start, make sure you have:

    • A VPS running Ubuntu 24.04 LTS with root or sudo access.
    • At least 4 GB RAM (8–12 GB strongly recommended for production; the JVM plus PostgreSQL plus OS is a real workload).
    • At least 20 GB disk (Keycloak + JDK + Postgres data + logs).
    • A public DNS record (for example auth.example.com) pointing at your VPS IPv4 address.
    • Ports 80 and 443 open in your firewall and reachable from the internet (required for Let's Encrypt HTTP-01 validation).
    Sizing tip. The CloudCore Professional plan (6 vCPU / 12 GB RAM / 100 GB NVMe) handles ~50k MAU with PostgreSQL co-located on the same box. For >100k MAU or high burst traffic, split PostgreSQL onto its own VPS and run two Keycloak nodes behind a load balancer.

    Connect to your server:

    bash
    ssh root@your-server-ip

    If you have not yet installed PostgreSQL or want a standalone database host, see our companion guide: How to Install PostgreSQL on Ubuntu 24.04.

    Step 1: Update the System and Create a Service User

    Refresh the package index and upgrade the system first.

    bash
    sudo apt update && sudo apt upgrade -y

    Create a dedicated unprivileged system user for Keycloak. Running the JVM as root is never a good idea.

    bash
    sudo groupadd --system keycloak
    sudo useradd --system --gid keycloak --home-dir /opt/keycloak --shell /sbin/nologin keycloak

    Create the install directory:

    bash
    sudo mkdir -p /opt/keycloak
    sudo chown keycloak:keycloak /opt/keycloak

    Install a few utilities you will use throughout the guide:

    bash
    sudo apt install -y curl wget unzip ca-certificates gnupg apt-transport-https software-properties-common ufw

    Step 2: Install JDK 21

    Keycloak 25+ requires JDK 17 or later; JDK 21 (the current LTS) is recommended for new installs. Ubuntu 24.04 ships it directly.

    bash
    sudo apt install -y openjdk-21-jdk-headless

    Verify:

    bash
    java -version

    Expected output:

    text
    openjdk version "21.0.x" 2024-xx-xx
    OpenJDK Runtime Environment (build 21.0.x+xx-Ubuntu-...)
    OpenJDK 64-Bit Server VM (build 21.0.x+xx-Ubuntu-..., mixed mode, sharing)

    Export JAVA_HOME for the service user (the systemd unit will re-declare this too):

    bash
    sudo tee /etc/profile.d/java-home.sh > /dev/null <<'EOF'
    export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
    export PATH=$JAVA_HOME/bin:$PATH
    EOF

    Step 3: Install and Configure PostgreSQL

    The embedded dev-only H2 database is fine for tinkering, but you must use PostgreSQL (or another supported RDBMS) for anything near production.

    Install PostgreSQL 16 from the Ubuntu archive:

    bash
    sudo apt install -y postgresql postgresql-contrib
    sudo systemctl enable --now postgresql

    Create the Keycloak database and role:

    bash
    sudo -u postgres psql <<'SQL'
    CREATE DATABASE keycloak;
    CREATE USER keycloak WITH ENCRYPTED PASSWORD 'CHANGE_ME_STRONG_PASSWORD';
    GRANT ALL PRIVILEGES ON DATABASE keycloak TO keycloak;
    ALTER DATABASE keycloak OWNER TO keycloak;
    \c keycloak
    GRANT ALL ON SCHEMA public TO keycloak;
    SQL

    Replace CHANGE_ME_STRONG_PASSWORD with a real one — you can generate a strong value with:

    bash
    openssl rand -base64 32

    Verify the connection:

    bash
    psql "host=127.0.0.1 dbname=keycloak user=keycloak password=CHANGE_ME_STRONG_PASSWORD" -c "SELECT version();"

    If Keycloak lives on the same server, keep PostgreSQL bound to localhost (the default in Ubuntu). For a separate database host, edit /etc/postgresql/16/main/postgresql.conf (listen_addresses) and pg_hba.conf accordingly, and open port 5432 only to the Keycloak VPS.

    Step 4: Download the Keycloak 25 Quarkus Distribution

    Pick a Keycloak release. At time of writing, the latest 25.x is recommended (adjust KC_VERSION if a newer release is out).

    bash
    export KC_VERSION=25.0.6
    cd /tmp
    wget https://github.com/keycloak/keycloak/releases/download/${KC_VERSION}/keycloak-${KC_VERSION}.zip
    unzip keycloak-${KC_VERSION}.zip
    sudo mv keycloak-${KC_VERSION}/* /opt/keycloak/
    sudo chown -R keycloak:keycloak /opt/keycloak

    Verify the layout:

    bash
    sudo ls /opt/keycloak

    You should see bin/, conf/, data/, lib/, providers/, themes/, and version.txt.

    Step 5: Configure Keycloak (keycloak.conf)

    Keycloak reads /opt/keycloak/conf/keycloak.conf on startup. Environment variables (KC_*) always override the file. Write a production-ready base config:

    bash
    sudo tee /opt/keycloak/conf/keycloak.conf > /dev/null <<'EOF'
    

    ---- Database ----

    db=postgres db-url=jdbc:postgresql://127.0.0.1:5432/keycloak db-username=keycloak db-password=CHANGE_ME_STRONG_PASSWORD

    ---- Hostname (public URL seen by browsers) ----

    hostname=https://auth.example.com hostname-strict=true hostname-backchannel-dynamic=false

    ---- HTTP / reverse proxy ----

    http-enabled=true http-host=127.0.0.1 http-port=8080 proxy-headers=xforwarded

    ---- Health & metrics (for Prometheus scraping) ----

    health-enabled=true metrics-enabled=true

    ---- Logging ----

    log=console,file log-level=INFO log-file=/opt/keycloak/data/log/keycloak.log EOF

    sudo chown keycloak:keycloak /opt/keycloak/conf/keycloak.conf sudo chmod 640 /opt/keycloak/conf/keycloak.conf

    Replace auth.example.com with your real DNS name and the password with the one you set in Step 3.

    Why these settings

    • db=postgres + db-url/db-username/db-password — tell Keycloak to use your PostgreSQL instance. These map to the KC_DB, KC_DB_URL, KC_DB_USERNAME, KC_DB_PASSWORD environment variables if you prefer env-based config.
    • hostname=https://auth.example.com + hostname-strict=true — Keycloak 25 uses the hostname-v2 options. Setting a full URL here locks the public base URL used in tokens, metadata, and emails; strict rejects mismatched Host headers.
    • http-enabled=true with http-host=127.0.0.1 — Keycloak listens locally on plain HTTP. TLS is terminated by Nginx. This is the standard reverse-proxy pattern.
    • proxy-headers=xforwarded — trust X-Forwarded-* headers from the reverse proxy so Keycloak knows the client's original scheme and IP.
    • health-enabled + metrics-enabled — expose /health and /metrics endpoints (bound to the management port 9000 by default) for Prometheus and load-balancer health checks.

    Step 6: Build and Bootstrap the Admin

    The Quarkus distribution uses a build step that pre-compiles config into an optimized image, then a start step at runtime. Run the build once as the keycloak user:

    bash
    sudo -u keycloak /opt/keycloak/bin/kc.sh build

    This produces an augmented server under /opt/keycloak/lib/quarkus and should finish in under a minute.

    Bootstrap the first admin

    In Keycloak 25, the bootstrap admin user is created from environment variables on first run. This admin is scoped to the master realm and is intended to be used once to create a real admin account, then deleted.

    bash
    export KC_BOOTSTRAP_ADMIN_USERNAME=tmpadmin
    export KC_BOOTSTRAP_ADMIN_PASSWORD="$(openssl rand -base64 24)"
    echo "Bootstrap admin password: $KC_BOOTSTRAP_ADMIN_PASSWORD"

    Run Keycloak once in the foreground to let it initialize the database and create the admin:

    bash
    sudo -E -u keycloak /opt/keycloak/bin/kc.sh start --optimized

    Wait for the log line:

    text
    Running the server in production mode. DO NOT use this configuration in production.
    ...
    Listening on: http://127.0.0.1:8080
    ...
    Admin console listening on http://127.0.0.1:9000

    Press Ctrl+C to stop. The schema is now created and the bootstrap admin exists. You will promote it to a real admin in Step 9. For now, continue to systemd so the server runs persistently.

    Step 7: Create a systemd Service

    Create the unit file:

    bash
    sudo tee /etc/systemd/system/keycloak.service > /dev/null <<'EOF'
    [Unit]
    Description=Keycloak Identity and Access Management
    After=network.target postgresql.service
    Wants=postgresql.service

    [Service] Type=simple User=keycloak Group=keycloak Environment="JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64" Environment="JAVA_OPTS_APPEND=-Xms1024m -Xmx3072m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m"

    Bootstrap admin — comment out after the permanent admin is created

    Environment="KC_BOOTSTRAP_ADMIN_USERNAME=tmpadmin" Environment="KC_BOOTSTRAP_ADMIN_PASSWORD=CHANGE_ME_BOOTSTRAP_PASSWORD" ExecStart=/opt/keycloak/bin/kc.sh start --optimized Restart=on-failure RestartSec=10 LimitNOFILE=102400

    Hardening

    NoNewPrivileges=true PrivateTmp=true ProtectSystem=full ProtectHome=true

    [Install] WantedBy=multi-user.target EOF

    Replace CHANGE_ME_BOOTSTRAP_PASSWORD with the password you generated in Step 6. The -Xmx3072m heap is a sensible default for a 12 GB VPS co-hosting PostgreSQL — shrink to -Xmx1536m on a 4 GB box, grow to -Xmx6144m on 16 GB+.

    Enable and start:

    bash
    sudo systemctl daemon-reload
    sudo systemctl enable --now keycloak
    sudo systemctl status keycloak --no-pager

    You should see Active: active (running). Tail the log as it starts:

    bash
    sudo tail -f /opt/keycloak/data/log/keycloak.log

    Wait for Keycloak ... on JVM ... started in X.Xs. Listening on: http://127.0.0.1:8080.

    Step 8: TLS via Nginx Reverse Proxy

    You have two options to add TLS:

    • Option A (recommended): Nginx reverse proxy — simpler certificate management with Certbot, easy HTTP → HTTPS redirect, battle-tested.
    • Option B: native TLS in Keycloak — skip Nginx; set https-certificate-file / https-certificate-key-file in keycloak.conf. Works but you own cert rotation and there is no HTTP/80 redirect out of the box.
    This guide uses Option A.

    Install Nginx and Certbot

    bash
    sudo apt install -y nginx certbot python3-certbot-nginx

    Open the firewall:

    bash
    sudo ufw allow OpenSSH
    sudo ufw allow 'Nginx Full'
    sudo ufw --force enable

    Create the Nginx site

    bash
    sudo tee /etc/nginx/sites-available/keycloak > /dev/null <<'EOF'
    server {
        listen 80;
        server_name auth.example.com;
        return 301 https://$host$request_uri;
    }

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

    # Certs are filled in by certbot on first run ssl_certificate /etc/letsencrypt/live/auth.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/auth.example.com/privkey.pem;

    # Modern TLS ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m;

    # Security headers add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always; add_header X-Content-Type-Options nosniff always; add_header X-Frame-Options SAMEORIGIN always; add_header Referrer-Policy strict-origin-when-cross-origin always;

    client_max_body_size 20m;

    location / { proxy_pass http://127.0.0.1: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_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Port $server_port;

    proxy_http_version 1.1; proxy_read_timeout 120s; proxy_send_timeout 120s; proxy_buffering off; } } EOF

    sudo ln -s /etc/nginx/sites-available/keycloak /etc/nginx/sites-enabled/ sudo rm -f /etc/nginx/sites-enabled/default sudo nginx -t sudo systemctl reload nginx

    Obtain the Let's Encrypt certificate:

    bash
    sudo certbot --nginx -d auth.example.com --redirect --agree-tos -m [email protected] -n

    Certbot auto-renews via a systemd timer — verify with systemctl list-timers | grep certbot.

    Test end to end:

    bash
    curl -I https://auth.example.com/

    You should see HTTP/2 302 redirecting to /realms/master/.... Open https://auth.example.com/admin/ in a browser — the Keycloak admin console login screen loads.

    Step 9: Create Your First Realm

    Log into the admin console at https://auth.example.com/admin/ with the bootstrap credentials (tmpadmin / your bootstrap password).

    Promote a permanent admin

  • In the top-left dropdown, stay in master realm.
  • Go to Users → Add user. Create [email protected] with the required fields.
  • Open the new user → Credentials tab → Set password (non-temporary).
  • Role mapping tab → Assign role → filter by clients master-realm, grant admin.
  • Log out, log back in as the new admin.
  • Stop Keycloak, remove the KC_BOOTSTRAP_ADMIN_* environment lines from /etc/systemd/system/keycloak.service, delete the tmpadmin user from the admin console, systemctl daemon-reload && systemctl start keycloak.
  • Create a product realm

    The master realm is reserved for administering Keycloak itself. All your apps and users belong in a separate realm.

  • Top-left dropdown → Create realm.
  • Name it myapp (or your product name). Click Create.
  • Realm settings → Login: turn on User registration, Forgot password, Remember me, Email as username as fits your UX.
  • Realm settings → Email: configure the SMTP server used for password resets and verification (Mailgun, SES, Postmark, or your own Postfix).
  • Authentication → Policies → Password policy: add length(12), notUsername, digits(1), specialChars(1).
  • Authentication → Required actions: enable Configure OTP to let users self-enroll TOTP, or enable Webauthn Register Passwordless for passkeys.
  • Step 10: Register an OIDC Client (Grafana + Custom App)

    OIDC is the right protocol for 99% of new apps. Keycloak issues an ID token + access token, your app verifies it.

    Example A: Grafana via OIDC

  • In the myapp realm, go to Clients → Create client.
  • Client type: OpenID Connect. Client ID: grafana. Name: Grafana. Next.
  • Client authentication: ON (confidential client). Authentication flow: Standard flow (Authorization Code). Next.
  • Valid redirect URIs: https://grafana.example.com/login/generic_oauth
  • Web origins: https://grafana.example.com
  • Save.
  • Open the Credentials tab, copy the Client secret — you will paste it into Grafana.
  • Then in /etc/grafana/grafana.ini:

    ini
    [auth.generic_oauth]
    enabled = true
    name = Keycloak
    allow_sign_up = true
    client_id = grafana
    client_secret = PASTE_THE_SECRET_HERE
    scopes = openid profile email
    auth_url = https://auth.example.com/realms/myapp/protocol/openid-connect/auth
    token_url = https://auth.example.com/realms/myapp/protocol/openid-connect/token
    api_url = https://auth.example.com/realms/myapp/protocol/openid-connect/userinfo
    role_attribute_path = contains(roles[], 'grafana-admin') && 'Admin' || contains(roles[], 'grafana-editor') && 'Editor' || 'Viewer'

    Restart Grafana, click Sign in with Keycloak — you are redirected to Keycloak, authenticate, and land back in Grafana with the mapped role.

    Example B: Your own SPA / API

    The same realm can serve any number of apps. For a browser SPA (React, Vue, Svelte) talking to a backend API:

    • Create a public client myapp-web with redirect URIs for your app and Web origins set to your SPA domain. Use PKCE (Authorization code flow with pkce-enabled).
    • Create a bearer-only client myapp-api — no login, used by your API to validate access tokens with the realm's JWKS at https://auth.example.com/realms/myapp/protocol/openid-connect/certs.
    • In the SPA, use keycloak-js or any standard OIDC library (oidc-client-ts, oauth4webapi).
    Every token your API receives is signed by the realm keys — validate signature + iss + aud + exp and you have authentication.

    Step 11: Register a SAML Client

    SAML is still required by many enterprise tools. The workflow is similar.

  • Clients → Create client.
  • Client type: SAML. Client ID: use the SAML Entity ID the app expects (for AWS SSO this is usually urn:amazon:webservices).
  • Valid redirect URIs / ACS URL: the SAML consumer URL from the app's docs.
  • Save. Open the client's Keys tab and confirm the signing key is generated.
  • Advanced → Fine grain SAML endpoint configuration — set Assertion Consumer Service POST Binding URL.
  • Client scopes → Add mapper — add email, firstName, lastName, role list mappers so the SAML assertion carries the expected attributes.
  • Download the IdP metadata from https://auth.example.com/realms/myapp/protocol/saml/descriptor and upload it to the target application.
  • Apps like Atlassian Cloud, Salesforce, Zoom, AWS IAM Identity Center, and Tableau all plug in via this flow.

    Step 12: Identity Providers — Google, GitHub, LDAP

    Identity brokering lets Keycloak delegate authentication to an upstream IdP while still owning the session, MFA, and role mapping.

    Google

  • In Google Cloud Console create an OAuth 2.0 Client ID (type: Web application).
  • Authorized redirect URI: https://auth.example.com/realms/myapp/broker/google/endpoint
  • In Keycloak: realm myapp → Identity providers → Add provider → Google.
  • Paste the Client ID and Client secret. Save.
  • A Sign in with Google button now appears on the realm's login screen.
  • GitHub

  • GitHub → Settings → Developer settings → OAuth Apps → New OAuth App.
  • Authorization callback URL: https://auth.example.com/realms/myapp/broker/github/endpoint
  • Copy the Client ID and generate a client secret.
  • Keycloak → Identity providers → Add provider → GitHub → paste the credentials.
  • In both cases, the First Login Flow controls what happens when a brand-new Google/GitHub user signs in — the default is to create a local Keycloak user linked to the upstream account.

    LDAP / Active Directory Federation

    Federation is different from brokering: Keycloak reads users and groups directly from LDAP (read-only or read-write) and treats them as first-class users.

  • Realm → User federation → Add LDAP provider.
  • Vendor: Active Directory (or Red Hat Directory Server, OpenLDAP, etc.).
  • Connection URL: ldaps://ldap.example.com:636
  • Bind DN: cn=keycloak-service,ou=Service Accounts,dc=example,dc=com
  • Bind credential: the service account password.
  • Users DN: ou=People,dc=example,dc=com
  • Edit mode: READ_ONLY (recommended) or WRITABLE.
  • Click Test connection and Test authentication. Save.
  • Click Sync all users — user objects are materialized into Keycloak's database.
  • Optionally add a group-ldap-mapper to federate LDAP groups into Keycloak groups and roles.
  • After this, LDAP users can log in to any OIDC/SAML app connected to the realm, and MFA rules configured in Keycloak apply to them even though their passwords live in AD.

    Step 13: Custom Themes

    Keycloak themes control the look of the login, account, and email pages. You can change the logo, colors, and copy without forking Keycloak.

    bash
    sudo -u keycloak mkdir -p /opt/keycloak/themes/myapp/login/resources/css
    sudo -u keycloak mkdir -p /opt/keycloak/themes/myapp/login/resources/img

    Create /opt/keycloak/themes/myapp/login/theme.properties:

    properties
    parent=keycloak.v2
    import=common/keycloak
    styles=css/login.css css/custom.css

    Drop a logo at themes/myapp/login/resources/img/logo.png and a css/custom.css that overrides colors and the logo URL.

    Then in the realm: Realm settings → Themes → Login theme → myapp. Save. Refresh the login page — your branding loads.

    For production, bake custom themes into a dedicated /opt/keycloak/themes/myapp/ tree and keep it under version control. After any theme change, Keycloak picks it up on restart — sudo systemctl restart keycloak.

    Step 14: Events, Audit, and Monitoring

    Keycloak has a powerful event system that records every login, logout, failed password, token refresh, and admin action.

  • Realm → Events → Event configs.
  • Enable Save events under User events (login, logout, login_error, register, reset_password, etc.).
  • Enable Save events under Admin events; optionally Include representation to capture the full object on admin changes.
  • Set Expiration to e.g. 30 days so the events table does not grow forever.
  • Query events:

    • Events → User events: filter by user, client, type, date.
    • Events → Admin events: audit every change an admin made and by whom.
    For SIEM/Elasticsearch integration, install the Keycloak Event Listener SPI (community providers exist for Kafka, webhook, syslog) into /opt/keycloak/providers/, re-run kc.sh build, and register the listener in the realm's event config.

    Prometheus metrics

    You already enabled metrics in Step 5. Scrape them via the management port:

    yaml
    # prometheus.yml
    scrape_configs:
      - job_name: keycloak
        metrics_path: /metrics
        static_configs:
          - targets: ['127.0.0.1:9000']

    Key metrics to alert on: http_server_requests_seconds_count (by status), jvm_memory_used_bytes, process_cpu_usage, agroal_active_count (DB pool), and keycloak_logins_total.

    If you are building a broader observability stack, see How to Install Authelia and How to Install Authentik to compare what each IAM exposes.

    Step 15: Backups with pg_dump

    Keycloak keeps all state in PostgreSQL — users, realms, clients, roles, sessions, events, keys. Back up the database and you back up Keycloak.

    Create the backup directory:

    bash
    sudo mkdir -p /var/backups/keycloak
    sudo chown postgres:postgres /var/backups/keycloak

    Write the backup script:

    bash
    sudo tee /usr/local/bin/keycloak-backup.sh > /dev/null <<'EOF'
    #!/usr/bin/env bash
    set -euo pipefail

    BACKUP_DIR=/var/backups/keycloak STAMP=$(date +%F_%H%M%S) DUMP_FILE="${BACKUP_DIR}/keycloak-${STAMP}.sql.gz"

    Dump with owner/privilege info, compressed

    sudo -u postgres pg_dump --clean --if-exists --no-owner keycloak \ | gzip -9 > "${DUMP_FILE}"

    Retain 14 daily dumps, delete older

    find "${BACKUP_DIR}" -name 'keycloak-*.sql.gz' -mtime +14 -delete

    echo "Backup complete: ${DUMP_FILE} ($(du -h "${DUMP_FILE}" | cut -f1))" EOF

    sudo chmod +x /usr/local/bin/keycloak-backup.sh

    Schedule it daily at 02:15:

    bash
    sudo tee /etc/cron.d/keycloak-backup > /dev/null <<'EOF'
    15 2   * root /usr/local/bin/keycloak-backup.sh >> /var/log/keycloak-backup.log 2>&1
    EOF

    Run once manually to verify:

    bash
    sudo /usr/local/bin/keycloak-backup.sh
    ls -lh /var/backups/keycloak/

    Restoring a backup

    On the target host (stop Keycloak first so schema migrations do not race):

    bash
    sudo systemctl stop keycloak
    gunzip -c /var/backups/keycloak/keycloak-2026-04-16_021500.sql.gz \
      | sudo -u postgres psql keycloak
    sudo systemctl start keycloak

    Ship the compressed dumps off-box with rsync, restic, or S3 — a backup that sits on the same VPS is not a backup. Our Restic install guide pairs well with this.

    Step 16: Upgrading Keycloak

    Keycloak publishes minor releases frequently and a major release yearly. Upgrades are deliberately conservative — schema migrations run automatically on startup, but you must always back up first.

    Upgrade checklist

  • Read the Keycloak Upgrading Guide for the target version — note any breaking changes (removed SPIs, changed defaults, deprecated options).
  • Back up the PostgreSQL database (/usr/local/bin/keycloak-backup.sh).
  • Back up the install directory:
  • bash
    sudo tar -czf /var/backups/keycloak/keycloak-app-$(date +%F).tar.gz /opt/keycloak

  • Stop Keycloak:
  • bash
    sudo systemctl stop keycloak

  • Download and extract the new version into a staging directory:
  • bash
    export KC_NEW=25.0.7
       cd /tmp
       wget https://github.com/keycloak/keycloak/releases/download/${KC_NEW}/keycloak-${KC_NEW}.zip
       unzip keycloak-${KC_NEW}.zip

  • Copy over your conf, themes, and providers:
  • bash
    sudo cp /opt/keycloak/conf/keycloak.conf /tmp/keycloak-${KC_NEW}/conf/
       sudo cp -r /opt/keycloak/themes/myapp /tmp/keycloak-${KC_NEW}/themes/
       sudo cp -r /opt/keycloak/providers/* /tmp/keycloak-${KC_NEW}/providers/ 2>/dev/null || true

  • Replace the install:
  • bash
    sudo mv /opt/keycloak /opt/keycloak.old
       sudo mv /tmp/keycloak-${KC_NEW} /opt/keycloak
       sudo chown -R keycloak:keycloak /opt/keycloak

  • Rebuild:
  • bash
    sudo -u keycloak /opt/keycloak/bin/kc.sh build

  • Start and tail the log:
  • bash
    sudo systemctl start keycloak
       sudo journalctl -u keycloak -f

    On first start with a new schema version, Keycloak runs database migrations — these can take seconds to minutes depending on user count. Look for Keycloak ... started in X.Xs.

  • Smoke test: log in as admin, log in as a user, test an OIDC flow end to end.
  • If everything is healthy, remove the old tree: sudo rm -rf /opt/keycloak.old.
  • For a major version upgrade (24 → 25, 25 → 26), do it first on a staging VPS with a copy of production's database and your apps pointed at it.

    Troubleshooting

    ProblemCauseSolution
    HTTPS required error on first loginhostname-strict=true with a mismatched public URL, or reverse proxy not forwarding X-Forwarded-ProtoConfirm hostname=https://auth.example.com matches the URL you visit. Ensure Nginx sets proxy_set_header X-Forwarded-Proto $scheme; and proxy-headers=xforwarded in keycloak.conf.
    FATAL: password authentication failed for user "keycloak"DB password mismatch between PostgreSQL and keycloak.confsudo -u postgres psql → ALTER USER keycloak WITH PASSWORD '...'; and update db-password= in keycloak.conf, restart.
    Admin console loads but CSS/JS 404sNginx stripping paths or compressing incorrectlyKeep location / as-is; do not rewrite paths. Disable proxy_buffering for admin console responsiveness.
    Invalid parameter: redirect_uri after Google/GitHub loginRedirect URI registered in Google/GitHub Console does not match Keycloak's broker endpointUse exactly https://auth.example.com/realms/myapp/broker/<alias>/endpoint.
    OutOfMemoryError: Metaspace under loadJVM metaspace too smallIncrease -XX:MaxMetaspaceSize=512m in the systemd unit.
    connection refused to 127.0.0.1:8080 after startKeycloak still starting or bound to a different hostsudo tail -f /opt/keycloak/data/log/keycloak.log until "Listening on". Confirm http-host=127.0.0.1.
    Users can log in but ID token's iss is wronghostname set to an internal URLSet hostname= to the exact public URL browsers and apps see (including scheme).
    Very slow login after upgradeMissed DB migration, or sessions table bloatedCheck journalctl -u keycloak for "Migration" lines. Run VACUUM ANALYZE; on the Keycloak database.
    Certbot renewal failsNginx config returns 301 for /.well-known/acme-challenge/The certbot --nginx plugin handles this automatically; do not add custom redirects above the ACME location.

    Where the logs live

    • Application log: /opt/keycloak/data/log/keycloak.log (rotated by Keycloak).
    • systemd journal: sudo journalctl -u keycloak -n 200 --no-pager.
    • Nginx access/error: /var/log/nginx/access.log, /var/log/nginx/error.log.
    • PostgreSQL: /var/log/postgresql/postgresql-16-main.log.

    FAQ

    Is Keycloak production-ready for B2C apps with tens of thousands of users?

    Yes. A single Keycloak node on a well-sized VPS (6 vCPU / 12 GB RAM) running against PostgreSQL comfortably serves tens of thousands of monthly active users. Beyond ~100k MAU or if you need zero-downtime deploys, run two nodes behind a TCP/HTTP load balancer with a shared PostgreSQL (managed or replicated). Keycloak clusters using the Quarkus distribution use Infinispan for distributed caches — by default it forms a cluster automatically when the nodes can reach each other over JGroups.

    Can I migrate users from Auth0 / Okta / Firebase Auth to Keycloak?

    Yes, with caveats. Passwords hashed with bcrypt/PBKDF2/Argon2 can be imported into Keycloak via a realm export JSON with credentialData containing the hash algorithm. If the source uses a proprietary hash you cannot read (common with Firebase Auth), the standard pattern is a just-in-time migration: stand up a custom UserStorage SPI that falls back to the old provider, re-hashes on first successful login, and stores the credential locally. Over a few weeks all active users migrate transparently.

    What is the difference between Keycloak, Authelia, and Authentik?

    All three are self-hosted IAM, but they target different spots. Authelia is a lightweight SSO portal focused on protecting web apps behind a reverse proxy (forward auth) with MFA — it does not speak OIDC as a full provider. Authentik is a modern OIDC/SAML provider with a slick UI, written in Python/Go, excellent for teams wanting something lighter than Keycloak. Keycloak is the enterprise heavyweight: largest ecosystem, Red Hat backing, richest feature set (UMA 2.0, CIBA, token exchange, fine-grained authz services), and the widest compatibility with enterprise SAML vendors. Choose Keycloak when you need protocol breadth, SPI extensibility, or enterprise SSO/SAML integrations.

    Do I have to use PostgreSQL?

    No — Keycloak supports PostgreSQL, MySQL, MariaDB, Oracle, and MSSQL. PostgreSQL is the most popular and best-documented choice for self-hosted installs. Avoid the embedded H2 database for anything except local development; it is explicitly not supported for production.

    How do I rotate the admin password and database password safely?

    For the admin password: log into the admin console as the admin user, Account security → Signing in → Password → Update password, and enroll MFA while you are there. For the database password: change it in PostgreSQL (ALTER USER keycloak WITH PASSWORD '...';), update db-password= in /opt/keycloak/conf/keycloak.conf (or the KC_DB_PASSWORD env var), and sudo systemctl restart keycloak. Keycloak will use the new credential on its next connection.

    Can I run Keycloak in Docker or Kubernetes instead?

    Yes. The official quay.io/keycloak/keycloak:25.0 image is supported and is what Red Hat uses internally. For Kubernetes, the Keycloak Operator manages the StatefulSet, PostgreSQL connection, and Ingress via custom resources. For a small-to-medium deployment on a single VPS, the native install described here uses fewer resources, starts faster, and is easier to debug.

    How do I expose only specific client apps to the public internet while keeping admin internal?

    Run two Nginx server blocks on separate domains. Public block (auth.example.com) proxies only /realms/… and /resources/… paths. Internal block (admin.auth.example.com) behind VPN or IP allowlist proxies /admin/…, /metrics, and /health. Set hostname-admin=https://admin.auth.example.com in keycloak.conf to tell Keycloak to emit the admin console on the internal hostname.

    Next Steps

    You now have a production-shape Keycloak:

    • Wire up your apps. Every new service you deploy — Grafana, Gitea, Mattermost, Nextcloud, Argo CD, Harbor, Jenkins — gets an OIDC client in your realm. One login, everywhere.
    • Turn on MFA by default. In Authentication → Required actions, make Configure OTP or Webauthn Register Passwordless a default action so every new user enrolls a second factor at first login.
    • Publish the realm's OIDC discovery URL to your app teams: https://auth.example.com/realms/myapp/.well-known/openid-configuration. That one URL is all any modern OIDC library needs.
    • Stand up high availability. Add a second Keycloak node and a managed PostgreSQL when you cross 50k MAU or need zero-downtime deploys.
    • Compare with the lighter IAM options. If your use case is mostly "protect my internal web apps behind MFA" rather than "be the OIDC provider for my SaaS", read our Authelia install guide and Authentik install guide.
    • Read the official docs. The Keycloak documentation is the canonical reference — especially the Server Administration Guide and the Securing Apps guide.
    Right-sized VPS for Keycloak. A single Keycloak + PostgreSQL node runs beautifully on the CloudCore Professional plan (6 vCPU / 12 GB RAM / 100 GB NVMe). EUR 19.99/month, flat — no per-MAU bill, ever.

    Was this article helpful?

    ← Back to Install GuidesBrowse all categories →

    Still have questions?

    Contact Support →Submit a Ticket