Install Keycloak SSO on VPS — Single Sign-On for All Apps
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
- Minimum: 2 vCPU, 2 GB RAM (Keycloak uses ~512 MB), 10 GB disk
- Recommended: 4 GB RAM for production with thousands of users
- Docker and Docker Compose installed
- Domain pointing to your VPS with SSL certificate (Keycloak requires HTTPS in production)
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
/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.