Is Keycloak Production Ready? A Practical Checklist
Last updated: March 2026
Keycloak works out of the box for development. Getting it production-ready is a different story. The default configuration uses an H2 in-memory database, has no clustering, runs over HTTP, and exposes the admin console to the world. None of that belongs in production.
This checklist covers every configuration area you need to address before deploying Keycloak to handle real users and real traffic. Each item includes the specific configuration changes required, not just a recommendation to “consider” doing something.
1. Database: Switch to PostgreSQL
Keycloak’s embedded H2 database is for development only. It does not support clustering, has no durability guarantees, and will lose data on restart unless configured for file-based persistence.
Use PostgreSQL. It is the best-supported database for Keycloak and the one tested most extensively by the Keycloak team.
Configuration
# Keycloak database configuration
KC_DB=postgres
KC_DB_URL=jdbc:postgresql://db-host:5432/keycloak
KC_DB_USERNAME=keycloak
KC_DB_PASSWORD=<strong-password-from-secrets-manager>
PostgreSQL Tuning
For a Keycloak database handling up to 10,000 concurrent sessions:
# postgresql.conf
max_connections = 200
shared_buffers = 2GB
effective_cache_size = 6GB
work_mem = 16MB
maintenance_work_mem = 512MB
wal_level = replica
max_wal_senders = 3
checkpoint_completion_target = 0.9
random_page_cost = 1.1 # For SSD storage
Set Keycloak’s connection pool size to match:
KC_DB_POOL_INITIAL_SIZE=25
KC_DB_POOL_MIN_SIZE=25
KC_DB_POOL_MAX_SIZE=100
Backups
Configure automated PostgreSQL backups using pg_dump or continuous archiving with WAL-E/pgBackRest:
# Daily logical backup
pg_dump -Fc -h db-host -U keycloak keycloak > /backups/keycloak_$(date +%Y%m%d).dump
# Restore
pg_restore -h db-host -U keycloak -d keycloak /backups/keycloak_20260414.dump
Test your restore procedure regularly. A backup you have never tested is not a backup.
For a quick local setup using PostgreSQL, use the Keycloak Docker Compose Generator which generates a production-like compose file with PostgreSQL by default.
2. Clustering and High Availability
A single Keycloak instance is a single point of failure. In production, run at least two nodes behind a load balancer.
Infinispan Configuration
Keycloak uses Infinispan for distributed caching. In production, you need to configure cache transport so nodes can discover each other.
For Kubernetes deployments, use DNS_PING:
<!-- conf/cache-ispn.xml -->
<transport stack="kubernetes" />
# Environment variables for Kubernetes discovery
KC_CACHE_STACK=kubernetes
JAVA_OPTS_KC_HEAP="-XX:MaxRAMPercentage=70.0"
For non-Kubernetes deployments, use JDBC_PING (stores discovery info in the database):
KC_CACHE_STACK=jdbc-ping
Load Balancer Configuration
Your load balancer must support sticky sessions (session affinity) for optimal performance, though Keycloak will work without them via the distributed cache:
# Nginx example
upstream keycloak {
ip_hash; # Sticky sessions
server keycloak-1:8080;
server keycloak-2:8080;
}
Health check endpoint:
location /health {
proxy_pass http://keycloak/health/ready;
}
Session Configuration
Tune session lifetimes in the Keycloak admin console or via the Admin REST API:
| Setting | Recommended Value | Notes |
|---|---|---|
| SSO Session Idle | 30 minutes | Time before idle sessions expire |
| SSO Session Max | 10 hours | Maximum session lifetime |
| Access Token Lifespan | 5 minutes | Keep short for security |
| Client Session Idle | 30 minutes | Client-level session timeout |
For details on session management strategies, see Skycloak’s Session Management feature.
3. TLS Termination
Never run Keycloak over plain HTTP in production. There are two approaches:
Option A: TLS at the Reverse Proxy (Recommended)
Terminate TLS at your reverse proxy (Nginx, HAProxy, or a cloud load balancer) and proxy to Keycloak over HTTP:
KC_PROXY_HEADERS=xforwarded
KC_HTTP_ENABLED=true
KC_HOSTNAME=auth.example.com
# Nginx TLS termination
server {
listen 443 ssl http2;
server_name auth.example.com;
ssl_certificate /etc/ssl/certs/auth.example.com.pem;
ssl_certificate_key /etc/ssl/private/auth.example.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
location / {
proxy_pass http://keycloak;
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_buffer_size 128k;
proxy_buffers 4 256k;
}
}
For more on reverse proxy configuration, see How to Run Keycloak Behind a Reverse Proxy.
Option B: TLS on Keycloak Directly
KC_HTTPS_CERTIFICATE_FILE=/opt/keycloak/certs/tls.crt
KC_HTTPS_CERTIFICATE_KEY_FILE=/opt/keycloak/certs/tls.key
KC_HOSTNAME=auth.example.com
4. Admin Console Security
The admin console is the keys to the kingdom. Protect it aggressively.
Restrict Admin Console Access
Use Keycloak’s hostname configuration to serve the admin console on a separate URL or disable it on public-facing nodes:
# Serve admin on a different hostname
KC_HOSTNAME=auth.example.com
KC_HOSTNAME_ADMIN=admin.internal.example.com
Alternatively, use network-level restrictions. In Skycloak, you can use the configurable WAF to restrict admin console access by IP. For self-hosted setups, block at the reverse proxy:
location /admin {
allow 10.0.0.0/8;
deny all;
proxy_pass http://keycloak;
}
Master Realm Hardening
The master realm controls all other realms. Lock it down:
- Change the default admin password immediately.
- Enable MFA for all master realm admin accounts.
- Create separate admin accounts per person — no shared credentials.
- Set aggressive session timeouts (5 minutes idle, 1 hour max).
See Secure Your Keycloak’s Master Realm for a detailed walkthrough.
5. Hostname Configuration
Keycloak embeds the hostname in tokens, SAML metadata, and OIDC discovery documents. Getting this wrong causes redirect failures and token validation errors.
# Production hostname configuration
KC_HOSTNAME=auth.example.com
KC_HOSTNAME_STRICT=true
KC_HOSTNAME_BACKCHANNEL_DYNAMIC=false
Setting KC_HOSTNAME_STRICT=true ensures Keycloak rejects requests that do not match the configured hostname. This prevents host header injection attacks.
If you encounter hostname-related issues, see Keycloak Redirect URI Mismatch Troubleshooting.
6. Monitoring and Observability
Health Endpoints
Keycloak exposes health endpoints that your orchestrator should use:
# Liveness (is the process running?)
curl http://keycloak:8080/health/live
# Readiness (can it accept traffic?)
curl http://keycloak:8080/health/ready
# Full health with details
curl http://keycloak:8080/health
Enable these in your Keycloak build:
KC_HEALTH_ENABLED=true
Metrics
Keycloak can expose Prometheus-compatible metrics:
KC_METRICS_ENABLED=true
Metrics are available at /metrics. Key metrics to monitor:
| Metric | Alert Threshold | Notes |
|---|---|---|
keycloak_request_duration_seconds |
p99 > 2s | Login latency |
vendor_cache_manager_default_cluster_size |
< expected nodes | Cluster split |
jvm_memory_used_bytes |
> 80% of max | Memory pressure |
db_pool_active_count |
> 80% of max | Connection pool exhaustion |
For dashboards and alerting, see how Skycloak’s Insights feature provides built-in monitoring for managed clusters.
Log Aggregation
Configure structured JSON logging for easier parsing:
KC_LOG=console
KC_LOG_CONSOLE_FORMAT=json
KC_LOG_LEVEL=info
KC_LOG_LEVEL_ORG_KEYCLOAK=info
Send logs to your aggregation platform (ELK, Loki, Datadog). Key events to alert on:
- Failed admin login attempts
- Client authentication failures
- Token validation errors
- Configuration changes
Keycloak’s event system captures user and admin events. Configure it in the realm settings to write events to the database or an external system. See Auditing in Keycloak: How to Catch Them All for event configuration and Skycloak’s Audit Logs feature for managed audit capabilities.
7. Rate Limiting and Brute Force Protection
Brute Force Protection
Enable brute force detection in each realm:
{
"bruteForceProtected": true,
"permanentLockout": false,
"maxFailureWaitSeconds": 900,
"minimumQuickLoginWaitSeconds": 60,
"waitIncrementSeconds": 60,
"quickLoginCheckMilliSeconds": 1000,
"maxDeltaTimeSeconds": 43200,
"failureFactor": 5
}
This locks accounts after 5 failed attempts with increasing wait times.
Rate Limiting at the Reverse Proxy
Keycloak does not have built-in request rate limiting. Implement it at the reverse proxy:
# Nginx rate limiting
limit_req_zone $binary_remote_addr zone=keycloak_login:10m rate=10r/s;
location /realms/*/protocol/openid-connect/token {
limit_req zone=keycloak_login burst=20 nodelay;
proxy_pass http://keycloak;
}
8. Theme Customization
Do not use the default Keycloak theme in production. At minimum, add your logo and brand colors so users know they are on a legitimate login page.
Create a custom theme JAR or mount a theme directory:
# Mount custom theme
docker run -v /path/to/themes:/opt/keycloak/themes quay.io/keycloak/keycloak:26.1.0 start
For a complete theming guide, see Keycloak Theme Customization: From Default to Branded Login. Skycloak’s Branding feature handles theme deployment and management for managed clusters.
9. Build Optimization
Keycloak introduced optimized builds in version 17. In production, always use a pre-built image:
FROM quay.io/keycloak/keycloak:26.1.0 AS builder
ENV KC_DB=postgres
ENV KC_HEALTH_ENABLED=true
ENV KC_METRICS_ENABLED=true
ENV KC_FEATURES=token-exchange,admin-fine-grained-authz
RUN /opt/keycloak/bin/kc.sh build
FROM quay.io/keycloak/keycloak:26.1.0
COPY --from=builder /opt/keycloak/ /opt/keycloak/
ENTRYPOINT ["/opt/keycloak/bin/kc.sh"]
CMD ["start", "--optimized"]
The --optimized flag skips the build phase at startup, reducing startup time significantly. This matters for Kubernetes rolling deployments where startup speed affects availability.
10. JVM Tuning
Keycloak runs on the JVM. Default settings are conservative. Tune for production:
# JVM settings
JAVA_OPTS_KC_HEAP="-XX:MaxRAMPercentage=70.0 -XX:InitialRAMPercentage=50.0"
KC_HTTP_POOL_MAX_THREADS=200
For a 4 GB container:
- Heap: ~2.8 GB (70% of RAM)
- Metaspace: Managed automatically by the JVM
- Thread stack: 1 MB per thread (200 threads = 200 MB)
- Remaining: OS overhead, off-heap buffers
Do not set -Xms and -Xmx directly when using percentage-based settings. They conflict.
11. Feature Flags
Keycloak has several features that are disabled by default. Enable only what you need:
# Enable specific features
KC_FEATURES=token-exchange,admin-fine-grained-authz,declarative-user-profile
# Disable features you do not need
KC_FEATURES_DISABLED=impersonation
Common features to consider:
| Feature | Use Case |
|---|---|
token-exchange |
Token exchange between services |
admin-fine-grained-authz |
Delegate admin permissions per realm |
declarative-user-profile |
Custom user profile validation |
organization |
Multi-tenancy with organizations |
12. Realm Configuration Export
Always keep a version-controlled export of your realm configuration. This is your disaster recovery plan:
# Export realm configuration
/opt/keycloak/bin/kc.sh export
--dir /opt/keycloak/data/export
--realm myrealm
--users realm_file
Store exports in git or your artifact repository. Automate exports as part of your CI/CD pipeline.
For infrastructure-as-code approaches, see Using Terraform to Set Up and Configure Keycloak.
The Complete Checklist
Use this as a final review before going live:
- [ ] PostgreSQL database with connection pooling and automated backups
- [ ] At least 2 Keycloak nodes with Infinispan clustering configured
- [ ] TLS termination with TLS 1.2+ and strong cipher suites
- [ ] Admin console restricted by network or separate hostname
- [ ] Master realm hardened with MFA and short session timeouts
- [ ] Hostname configuration set with
KC_HOSTNAME_STRICT=true - [ ] Health endpoints enabled and integrated with your orchestrator
- [ ] Prometheus metrics enabled and dashboarded
- [ ] Structured JSON logging shipped to your aggregation platform
- [ ] Brute force protection enabled in all realms
- [ ] Rate limiting at the reverse proxy
- [ ] Custom login theme deployed
- [ ] Optimized Docker image built with only required features
- [ ] JVM memory and thread pool tuned for expected load
- [ ] Realm configuration exported and version-controlled
- [ ] Session lifetimes reviewed and tightened
- [ ] Disaster recovery procedure tested
When Self-Hosting Is Too Much
This checklist has 17 items, each with sub-tasks. Maintaining a production Keycloak cluster requires ongoing work: security patches, version upgrades, certificate rotation, database maintenance, cache tuning, and capacity planning.
If that operational burden is more than your team can absorb, consider a managed option. Skycloak handles all of the items on this checklist — database management, clustering, TLS, monitoring, backups, and security hardening — so your team can focus on building your application rather than maintaining infrastructure. See our SLA for uptime guarantees and security practices for compliance details.
Check Skycloak pricing to compare the cost against self-hosting.
Ready to simplify your authentication?
Deploy production-ready Keycloak in minutes. Unlimited users, flat pricing, no SSO tax.