Install Keycloak SSO Single Sign-On on VPS

Install Keycloak SSO on VPS — Single Sign-On for All Apps

June 2026 · 9 min read

Keycloak is an open-source Identity and Access Management (IAM) solution from Red Hat that lets you build Single Sign-On (SSO) — log in once and access all applications. It supports OpenID Connect, OAuth 2.0, and SAML 2.0 with built-in login pages, user and role management via admin console, integration with Google/GitHub social login, and MFA setup without writing authentication code yourself. This guide covers installation via Docker, realm and client setup, and connecting Node.js/PHP applications to use Keycloak for login.

System Requirements

Install Keycloak with Docker Compose

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

cat > docker-compose.yml << 'EOF'
version: "3.9"

services:
  postgres:
    image: postgres:15
    restart: unless-stopped
    environment:
      POSTGRES_DB: keycloak
      POSTGRES_USER: keycloak
      POSTGRES_PASSWORD: CHANGE_ME_DB_PASS
    volumes:
      - postgres_data:/var/lib/postgresql/data

  keycloak:
    image: quay.io/keycloak/keycloak:24.0
    restart: unless-stopped
    command: start
    environment:
      KC_DB: postgres
      KC_DB_URL: jdbc:postgresql://postgres:5432/keycloak
      KC_DB_USERNAME: keycloak
      KC_DB_PASSWORD: CHANGE_ME_DB_PASS
      KC_HOSTNAME: auth.yourdomain.com
      KC_PROXY: edge
      KC_HTTP_ENABLED: "true"
      KEYCLOAK_ADMIN: admin
      KEYCLOAK_ADMIN_PASSWORD: CHANGE_ME_ADMIN_PASS
    ports:
      - "127.0.0.1:8080:8080"
    depends_on:
      - postgres

volumes:
  postgres_data:
EOF

docker-compose up -d

Set Up Nginx Reverse Proxy

nano /etc/nginx/sites-available/keycloak

server {
    listen 80;
    server_name auth.yourdomain.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name auth.yourdomain.com;

    ssl_certificate     /etc/letsencrypt/live/auth.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/auth.yourdomain.com/privkey.pem;

    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_buffer_size 128k;
        proxy_buffers 4 256k;
        proxy_busy_buffers_size 256k;
    }
}

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

# Request SSL certificate
certbot --nginx -d auth.yourdomain.com

Set Up Realm and Client

Open the Admin Console at https://auth.yourdomain.com/admin and follow these steps:

# Steps via UI (Admin Console)
# 1. Create a new Realm:
#    "Create Realm" → set a name, e.g. "myapp"

# 2. Create a Client for your application:
#    Clients → Create client
#    Client ID: myapp-backend
#    Client type: OpenID Connect
#    Valid redirect URIs: https://myapp.com/*
#    Web origins: https://myapp.com

# 3. Create Roles:
#    Realm roles → Create role → "admin" / "user"

# 4. Create a User:
#    Users → Add user → set username/email
#    Credentials tab → Set password
#    Role Mappings → assign roles

Connect Your Application to Keycloak (Node.js)

# Install Keycloak adapter
npm install keycloak-connect express-session

# app.js
const Keycloak = require('keycloak-connect');
const session = require('express-session');
const express = require('express');

const app = express();
const memoryStore = new session.MemoryStore();

app.use(session({
  secret: 'your-session-secret',
  resave: false,
  saveUninitialized: true,
  store: memoryStore
}));

const keycloak = new Keycloak({ store: memoryStore }, {
  realm: 'myapp',
  'auth-server-url': 'https://auth.yourdomain.com',
  'ssl-required': 'external',
  resource: 'myapp-backend',
  'public-client': true
});

app.use(keycloak.middleware());

// Protected route — requires login
app.get('/dashboard', keycloak.protect(), (req, res) => {
  res.json({ user: req.kauth.grant.access_token.content });
});

// Protected route for admin only
app.get('/admin', keycloak.protect('realm:admin'), (req, res) => {
  res.json({ message: 'Admin area' });
});

Set Up Social Login (Google)

# In Admin Console:
# Identity Providers → Add provider → Google
# Enter Client ID and Client Secret from Google Console
# (console.cloud.google.com → APIs & Services → Credentials)

# Redirect URI to enter in Google Console:
# https://auth.yourdomain.com/realms/myapp/broker/google/endpoint

# Configure auto-link existing account:
# Identity Providers → Google → Mappers → Add mapper
# Type: Hardcoded Role → select the role to assign

Enable MFA (TOTP)

# In Admin Console:
# Authentication → Required Actions
# Enable "Configure OTP" → Set as Default Action

# Or enforce MFA per Role:
# Authentication → Flows → Browser flow
# OTP Form → Required

# Users will be prompted to set up TOTP via Google Authenticator
# on their first login after enabling Required Action

For VPS running Keycloak, choose a server with sufficient SSD and RAM — asiagb's Thai VPS service offers RAM 4 GB and up at competitive pricing with Thai language support for DNS configuration before connecting Keycloak to your domain. If you need to verify the SSL health of auth.yourdomain.com before go-live, use dnsxray.com to check your certificate chain for free.


Verify Keycloak is Working Correctly

# Check all containers running
docker-compose ps

# View keycloak logs
docker-compose logs -f keycloak --tail=50

# Verify OIDC discovery endpoint
curl -s https://auth.yourdomain.com/realms/myapp/.well-known/openid-configuration | python3 -m json.tool | head -20

# Test token endpoint with grant
curl -s -X POST \
  "https://auth.yourdomain.com/realms/myapp/protocol/openid-connect/token" \
  -d "client_id=myapp-backend&username=testuser&password=testpass&grant_type=password" \
  | python3 -m json.tool | grep access_token | head -3

# Check admin API
curl -s "https://auth.yourdomain.com/admin/realms/myapp/users" \
  -H "Authorization: Bearer $(cat /tmp/admin_token.txt)" | python3 -m json.tool
Tip: Keycloak has health check endpoints at /health and /health/ready — use them to set up Docker healthchecks or Uptime Kuma alerts so you're notified before users report downtime.

Common Issues & Troubleshooting

Error: "Invalid redirect_uri" During Login

The redirect URI sent by your app doesn't match the Client configuration.

# In Admin Console: Clients → [client] → Valid Redirect URIs
# Set the correct URL, e.g.:
# https://myapp.com/*
# http://localhost:3000/*  (for development)

# Watch for trailing slashes — "https://myapp.com" ≠ "https://myapp.com/"
# If unsure, add both

Keycloak Shows "HTTPS required" Even With SSL Configured

Keycloak isn't seeing X-Forwarded-Proto from the reverse proxy.

# Verify Nginx sends the header correctly
proxy_set_header X-Forwarded-Proto $scheme;

# Keycloak config must have KC_PROXY=edge set
# If still failing in admin console, try:
# Realm Settings → General → Require SSL → "External requests only"

Keycloak Very Slow on First Start

JVM warmup normally takes 30–60 seconds on first run.

# Increase JVM heap size if RAM allows
# In docker-compose.yml environment:
JAVA_OPTS_APPEND: "-Xms512m -Xmx1024m"

# Set a longer health check timeout
healthcheck:
  test: ["CMD", "curl", "-f", "http://localhost:8080/health/ready"]
  interval: 30s
  timeout: 10s
  retries: 5
  start_period: 90s

Error: "Failed to connect to database" on Startup

Keycloak starts before PostgreSQL is ready — set depends_on correctly.

# In docker-compose.yml
services:
  keycloak:
    depends_on:
      postgres:
        condition: service_healthy

  postgres:
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "keycloak"]
      interval: 10s
      timeout: 5s
      retries: 5

Frequently Asked Questions

How is Keycloak different from Authelia?

Keycloak is a full-featured IAM solution — it includes user management, roles/groups, social login, SAML, and fine-grained access control built-in, making it ideal for organizations needing a complete identity system. Authelia is a lighter authentication middleware for protecting web apps via forward auth in Nginx/Traefik without modifying application code.

How many users can Keycloak handle?

Keycloak can support millions of users per realm, but performance depends on your VPS database and RAM. With 4 GB RAM and PostgreSQL on SSD, you can comfortably support thousands of concurrent logins. For larger scale, set up a Keycloak cluster.

Can I migrate users from an existing app to Keycloak?

Yes — via User Federation for LDAP/Active Directory, or import using the Admin API for users in an existing database. You can also write a custom Java user storage provider if your source data isn't in a standard format.