Set up Keycloak as an enterprise identity provider with PostgreSQL backend, SSL encryption, and high availability clustering for OAuth2, OpenID Connect, and SAML authentication across your organization.
Prerequisites
- Root or sudo access
- 2GB+ RAM recommended
- PostgreSQL database
- Valid domain name for SSL
- Open ports 8080, 8443, 7800
What this solves
Keycloak provides centralized identity and access management for enterprise applications, supporting OAuth2, OpenID Connect, and SAML protocols. This tutorial configures a production-ready Keycloak cluster with PostgreSQL database backend, SSL encryption, LDAP integration, and high availability clustering for enterprise environments.
Step-by-step installation
Update system packages
Start by updating your package manager to ensure you get the latest versions and security patches.
sudo apt update && sudo apt upgrade -y
Install Java 17
Keycloak requires Java 17 or later. Install OpenJDK 17 which provides the best compatibility and performance.
sudo apt install -y openjdk-17-jdk wget curl unzip
java -version
Create keycloak user and directories
Create a dedicated system user for running Keycloak services and set up the directory structure with proper permissions.
sudo useradd -r -s /bin/false keycloak
sudo mkdir -p /opt/keycloak
sudo mkdir -p /var/log/keycloak
sudo mkdir -p /etc/keycloak
Download and install Keycloak
Download the latest Keycloak distribution and extract it to the installation directory.
cd /tmp
wget https://github.com/keycloak/keycloak/releases/download/24.0.1/keycloak-24.0.1.tar.gz
sudo tar -xzf keycloak-24.0.1.tar.gz -C /opt
sudo mv /opt/keycloak-24.0.1/* /opt/keycloak/
sudo chown -R keycloak:keycloak /opt/keycloak
sudo chown -R keycloak:keycloak /var/log/keycloak
sudo chmod 755 /opt/keycloak
sudo chmod 755 /var/log/keycloak
Install and configure PostgreSQL
Set up PostgreSQL as the database backend for Keycloak. This provides better performance and reliability than the default H2 database.
sudo apt install -y postgresql postgresql-contrib
sudo systemctl enable --now postgresql
Create Keycloak database and user
Create a dedicated database and user for Keycloak with appropriate permissions.
sudo -u postgres createdb keycloak
sudo -u postgres psql -c "CREATE USER keycloak WITH ENCRYPTED PASSWORD 'KC_SecurePass2024!';"
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE keycloak TO keycloak;"
sudo -u postgres psql -c "ALTER DATABASE keycloak OWNER TO keycloak;"
Download PostgreSQL JDBC driver
Keycloak needs the PostgreSQL JDBC driver to connect to the database.
sudo mkdir -p /opt/keycloak/providers
cd /tmp
wget https://jdbc.postgresql.org/download/postgresql-42.7.1.jar
sudo mv postgresql-42.7.1.jar /opt/keycloak/providers/
sudo chown keycloak:keycloak /opt/keycloak/providers/postgresql-42.7.1.jar
Configure Keycloak database connection
Create the main Keycloak configuration file with database settings and clustering configuration.
# Database configuration
db=postgres
db-url=jdbc:postgresql://localhost:5432/keycloak
db-username=keycloak
db-password=KC_SecurePass2024!
HTTP/HTTPS configuration
http-host=0.0.0.0
http-port=8080
https-port=8443
hostname-strict=false
hostname-strict-https=false
Clustering configuration
cache=ispn
cache-stack=tcp
cluster-stack=tcp
Logging
log-level=INFO
log-file=/var/log/keycloak/keycloak.log
Health and metrics
health-enabled=true
metrics-enabled=true
Features
features=account2,account-api,admin-fine-grained-authz,admin2,authorization,ciba,client-policies,declarative-user-profile,docker,impersonation,openshift-integration,scripts,token-exchange,web-authn
Set proper ownership and permissions for the configuration file.
sudo chown keycloak:keycloak /opt/keycloak/conf/keycloak.conf
sudo chmod 600 /opt/keycloak/conf/keycloak.conf
Build Keycloak with custom configuration
Build Keycloak with the PostgreSQL driver and configuration optimizations.
cd /opt/keycloak
sudo -u keycloak /opt/keycloak/bin/kc.sh build
Install SSL certificates with Let's Encrypt
Install Certbot to obtain SSL certificates for secure HTTPS communication.
sudo apt install -y certbot
sudo certbot certonly --standalone -d auth.example.com --email admin@example.com --agree-tos --non-interactive
Configure SSL keystore
Convert Let's Encrypt certificates to Java keystore format for Keycloak.
sudo mkdir -p /opt/keycloak/conf/ssl
sudo openssl pkcs12 -export -in /etc/letsencrypt/live/auth.example.com/fullchain.pem -inkey /etc/letsencrypt/live/auth.example.com/privkey.pem -out /tmp/keystore.p12 -name keycloak -passout pass:KeystorePass2024!
sudo keytool -importkeystore -deststorepass KeystorePass2024! -destkeypass KeystorePass2024! -destkeystore /opt/keycloak/conf/ssl/keycloak.jks -srckeystore /tmp/keystore.p12 -srcstoretype PKCS12 -srcstorepass KeystorePass2024! -alias keycloak
sudo chown -R keycloak:keycloak /opt/keycloak/conf/ssl
sudo chmod 600 /opt/keycloak/conf/ssl/keycloak.jks
sudo rm /tmp/keystore.p12
Update configuration for HTTPS
Add SSL keystore configuration to the Keycloak configuration file.
sudo tee -a /opt/keycloak/conf/keycloak.conf > /dev/null << 'EOF'
SSL/TLS configuration
https-certificate-file=/etc/letsencrypt/live/auth.example.com/fullchain.pem
https-certificate-key-file=/etc/letsencrypt/live/auth.example.com/privkey.pem
hostname=auth.example.com
EOF
Create systemd service
Create a systemd service file to manage Keycloak as a system service with proper resource limits and security settings.
[Unit]
Description=Keycloak Identity Provider
After=network.target postgresql.service
Requires=postgresql.service
[Service]
Type=exec
User=keycloak
Group=keycloak
ExecStart=/opt/keycloak/bin/kc.sh start --optimized
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure
RestartSec=30s
Security settings
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ReadWritePaths=/var/log/keycloak /opt/keycloak/data
Resource limits
LimitNOFILE=65536
LimitNPROC=32768
Environment
Environment=JAVA_OPTS="-Xms1g -Xmx2g -XX:MetaspaceSize=96m -XX:MaxMetaspaceSize=256m"
Environment=KC_LOG_FILE=/var/log/keycloak/keycloak.log
Working directory
WorkingDirectory=/opt/keycloak
[Install]
WantedBy=multi-user.target
Configure firewall rules
Open the necessary ports for Keycloak HTTP, HTTPS, and clustering communication.
sudo ufw allow 8080/tcp comment 'Keycloak HTTP'
sudo ufw allow 8443/tcp comment 'Keycloak HTTPS'
sudo ufw allow 7800/tcp comment 'Keycloak clustering'
sudo ufw allow 57800/tcp comment 'Keycloak clustering multicast'
Start and enable Keycloak
Enable and start the Keycloak service, then check its status to ensure it's running properly.
sudo systemctl daemon-reload
sudo systemctl enable --now keycloak
sudo systemctl status keycloak
sudo journalctl -u keycloak -f
Create initial admin user
Create the first admin user for accessing the Keycloak admin console.
cd /opt/keycloak
sudo -u keycloak /opt/keycloak/bin/kc.sh start --optimized &
sleep 30
sudo -u keycloak KEYCLOAK_ADMIN=admin KEYCLOAK_ADMIN_PASSWORD='AdminPass2024!' /opt/keycloak/bin/kc.sh start --optimized
sudo pkill -f keycloak
sudo systemctl start keycloak
Configure clustering for high availability
For the second node in your cluster, modify the configuration to enable clustering. On the second server, follow all previous steps and modify the configuration.
# Add these lines for clustering on second node
cache=ispn
cache-stack=tcp
cluster-stack=tcp
cluster=keycloak-cluster
JGroups TCP configuration
kc.spi-connections-jpa-default-url=jdbc:postgresql://203.0.113.10:5432/keycloak
kc.spi-connections-jpa-default-username=keycloak
kc.spi-connections-jpa-default-password=KC_SecurePass2024!
Cluster discovery
kc.cache-config-file=cluster-tcp.xml
Configure LDAP integration
Set up LDAP user federation through the admin console. Access https://auth.example.com:8443/admin and navigate to User Federation.
Configure reverse proxy with NGINX
Set up NGINX as a reverse proxy for SSL termination and load balancing. You can reference our NGINX configuration tutorial for detailed setup.
upstream keycloak {
server 127.0.0.1:8080;
# Add second node for clustering
# server 203.0.113.11:8080;
}
server {
listen 443 ssl http2;
server_name auth.example.com;
ssl_certificate /etc/letsencrypt/live/auth.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/auth.example.com/privkey.pem;
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-Port $server_port;
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
}
}
server {
listen 80;
server_name auth.example.com;
return 301 https://$server_name$request_uri;
}
Performance tuning and security hardening
Configure JVM memory settings
Optimize JVM settings for production workloads based on your server resources.
# Add JVM options for production
kc.java-opts=-Xms2g -Xmx4g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true
Configure security headers
Add security headers and configure secure session settings through the admin console or configuration.
sudo tee -a /opt/keycloak/conf/keycloak.conf > /dev/null << 'EOF'
Security settings
kc.spi-login-protocol-openid-connect-legacy-logout-redirect-uri=true
kc.spi-truststore-file-file=/opt/keycloak/conf/ssl/truststore.jks
kc.spi-truststore-file-password=TruststorePass2024!
EOF
Set up automated certificate renewal
Configure automatic SSL certificate renewal with a cron job.
sudo crontab -e
0 3 * /usr/bin/certbot renew --quiet --post-hook "systemctl reload nginx"
Verify your setup
Test your Keycloak installation and verify all components are working correctly.
# Check service status
sudo systemctl status keycloak
sudo systemctl status postgresql
sudo systemctl status nginx
Check ports are listening
sudo netstat -tlnp | grep -E ':(8080|8443|5432)'
Test database connection
sudo -u postgres psql -d keycloak -c "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'public';"
Check logs for errors
sudo tail -f /var/log/keycloak/keycloak.log
sudo journalctl -u keycloak --no-pager -l
Test HTTPS access
curl -I https://auth.example.com:8443/health
curl -I https://auth.example.com/auth/realms/master
Access the admin console at https://auth.example.com/admin and log in with your admin credentials. You should see the Keycloak admin interface with master realm configuration options.
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Service fails to start | Database connection error | Check PostgreSQL status and credentials: sudo systemctl status postgresql |
| SSL certificate errors | Incorrect certificate path or permissions | Verify certificate paths and run sudo chown keycloak:keycloak /opt/keycloak/conf/ssl/* |
| Out of memory errors | Insufficient JVM heap size | Increase Xmx value in keycloak.conf based on available RAM |
| Clustering not working | Firewall blocking cluster ports | Ensure ports 7800 and 57800 are open between cluster nodes |
| LDAP authentication fails | Incorrect bind credentials or DN | Test LDAP connection with ldapsearch -H ldap://server -D binddn -W -b basedn |
| Cannot access admin console | Admin user not created properly | Recreate admin user with environment variables during startup |
| Database migration errors | Insufficient database permissions | Grant full permissions: sudo -u postgres psql -c "ALTER USER keycloak CREATEDB;" |
Next steps
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Default values
KEYCLOAK_VERSION="24.0.1"
POSTGRES_JDBC_VERSION="42.7.1"
KEYCLOAK_DB_PASSWORD="KC_SecurePass2024!"
ADMIN_USER="admin"
ADMIN_PASSWORD=""
# Auto-detect distribution
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_INSTALL="apt install -y"
PKG_UPDATE="apt update && apt upgrade -y"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf update -y"
;;
amzn)
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum update -y"
;;
*)
echo -e "${RED}Unsupported distribution: $ID${NC}"
exit 1
;;
esac
else
echo -e "${RED}Cannot detect distribution${NC}"
exit 1
fi
usage() {
echo "Usage: $0 [--admin-password PASSWORD] [--help]"
echo " --admin-password: Set Keycloak admin password (required)"
echo " --help: Show this help message"
exit 1
}
cleanup() {
echo -e "${RED}Installation failed. Cleaning up...${NC}"
systemctl stop keycloak || true
systemctl stop postgresql || true
}
trap cleanup ERR
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--admin-password)
ADMIN_PASSWORD="$2"
shift 2
;;
--help)
usage
;;
*)
echo -e "${RED}Unknown option: $1${NC}"
usage
;;
esac
done
if [[ -z "$ADMIN_PASSWORD" ]]; then
echo -e "${RED}Admin password is required${NC}"
usage
fi
# Check prerequisites
if [[ $EUID -ne 0 ]]; then
echo -e "${RED}This script must be run as root${NC}"
exit 1
fi
echo -e "${GREEN}[1/12] Updating system packages...${NC}"
$PKG_UPDATE
echo -e "${GREEN}[2/12] Installing Java 17 and required packages...${NC}"
if [[ "$PKG_MGR" == "apt" ]]; then
$PKG_INSTALL openjdk-17-jdk wget curl unzip postgresql postgresql-contrib
systemctl enable --now postgresql
else
$PKG_INSTALL java-17-openjdk java-17-openjdk-devel wget curl unzip postgresql-server postgresql-contrib
if [[ ! -f /var/lib/pgsql/data/postgresql.conf ]]; then
postgresql-setup --initdb
fi
systemctl enable --now postgresql
fi
java -version
echo -e "${GREEN}[3/12] Creating keycloak user and directories...${NC}"
if ! id keycloak &>/dev/null; then
useradd -r -s /bin/false keycloak
fi
mkdir -p /opt/keycloak /var/log/keycloak /etc/keycloak
chown keycloak:keycloak /var/log/keycloak
chmod 755 /var/log/keycloak
echo -e "${GREEN}[4/12] Downloading and installing Keycloak...${NC}"
cd /tmp
wget -O keycloak-${KEYCLOAK_VERSION}.tar.gz "https://github.com/keycloak/keycloak/releases/download/${KEYCLOAK_VERSION}/keycloak-${KEYCLOAK_VERSION}.tar.gz"
tar -xzf keycloak-${KEYCLOAK_VERSION}.tar.gz -C /opt
if [[ -d /opt/keycloak-${KEYCLOAK_VERSION} ]]; then
rsync -a /opt/keycloak-${KEYCLOAK_VERSION}/ /opt/keycloak/
rm -rf /opt/keycloak-${KEYCLOAK_VERSION}
fi
chown -R keycloak:keycloak /opt/keycloak
chmod 755 /opt/keycloak
echo -e "${GREEN}[5/12] Creating Keycloak database and user...${NC}"
sudo -u postgres createdb keycloak || echo "Database already exists"
sudo -u postgres psql -c "CREATE USER keycloak WITH ENCRYPTED PASSWORD '$KEYCLOAK_DB_PASSWORD';" || echo "User already exists"
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE keycloak TO keycloak;"
sudo -u postgres psql -c "ALTER DATABASE keycloak OWNER TO keycloak;"
echo -e "${GREEN}[6/12] Installing PostgreSQL JDBC driver...${NC}"
mkdir -p /opt/keycloak/providers
cd /tmp
wget -O postgresql-${POSTGRES_JDBC_VERSION}.jar "https://jdbc.postgresql.org/download/postgresql-${POSTGRES_JDBC_VERSION}.jar"
mv postgresql-${POSTGRES_JDBC_VERSION}.jar /opt/keycloak/providers/
chown keycloak:keycloak /opt/keycloak/providers/postgresql-${POSTGRES_JDBC_VERSION}.jar
echo -e "${GREEN}[7/12] Creating Keycloak configuration...${NC}"
cat > /opt/keycloak/conf/keycloak.conf << EOF
# Database configuration
db=postgres
db-url=jdbc:postgresql://localhost:5432/keycloak
db-username=keycloak
db-password=$KEYCLOAK_DB_PASSWORD
# HTTP/HTTPS configuration
http-host=0.0.0.0
http-port=8080
https-port=8443
hostname-strict=false
hostname-strict-https=false
# Clustering configuration
cache=ispn
cache-stack=tcp
cluster-stack=tcp
# Logging
log-level=INFO
log-file=/var/log/keycloak/keycloak.log
# Health and metrics
health-enabled=true
metrics-enabled=true
# Features
features=account2,account-api,admin-fine-grained-authz,admin2,authorization,ciba,client-policies,declarative-user-profile,docker,impersonation,openshift-integration,scripts,token-exchange,web-authn
EOF
chown keycloak:keycloak /opt/keycloak/conf/keycloak.conf
chmod 600 /opt/keycloak/conf/keycloak.conf
echo -e "${GREEN}[8/12] Building Keycloak with custom configuration...${NC}"
cd /opt/keycloak
sudo -u keycloak ./bin/kc.sh build
echo -e "${GREEN}[9/12] Creating systemd service...${NC}"
cat > /etc/systemd/system/keycloak.service << EOF
[Unit]
Description=Keycloak Identity Provider
After=network.target postgresql.service
Requires=postgresql.service
[Service]
Type=exec
User=keycloak
Group=keycloak
ExecStart=/opt/keycloak/bin/kc.sh start
Environment=KEYCLOAK_ADMIN=$ADMIN_USER
Environment=KEYCLOAK_ADMIN_PASSWORD=$ADMIN_PASSWORD
WorkingDirectory=/opt/keycloak
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
SyslogIdentifier=keycloak
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable keycloak
echo -e "${GREEN}[10/12] Configuring firewall...${NC}"
if command -v firewall-cmd &> /dev/null; then
firewall-cmd --add-port=8080/tcp --permanent
firewall-cmd --add-port=8443/tcp --permanent
firewall-cmd --reload
elif command -v ufw &> /dev/null; then
ufw allow 8080/tcp
ufw allow 8443/tcp
fi
echo -e "${GREEN}[11/12] Starting Keycloak service...${NC}"
systemctl start keycloak
echo -e "${GREEN}[12/12] Verifying installation...${NC}"
sleep 30
if systemctl is-active --quiet keycloak; then
echo -e "${GREEN}✓ Keycloak service is running${NC}"
else
echo -e "${RED}✗ Keycloak service failed to start${NC}"
systemctl status keycloak
exit 1
fi
if systemctl is-active --quiet postgresql; then
echo -e "${GREEN}✓ PostgreSQL service is running${NC}"
else
echo -e "${RED}✗ PostgreSQL service is not running${NC}"
exit 1
fi
# Test HTTP endpoint
if curl -f http://localhost:8080/health/ready &>/dev/null; then
echo -e "${GREEN}✓ Keycloak is responding to HTTP requests${NC}"
else
echo -e "${YELLOW}⚠ Keycloak may still be starting up${NC}"
fi
echo -e "${GREEN}Keycloak installation completed successfully!${NC}"
echo -e "${YELLOW}Access Keycloak at: http://$(hostname -I | awk '{print $1}'):8080${NC}"
echo -e "${YELLOW}Admin console: http://$(hostname -I | awk '{print $1}'):8080/admin${NC}"
echo -e "${YELLOW}Admin username: $ADMIN_USER${NC}"
echo -e "${YELLOW}Admin password: [provided during installation]${NC}"
echo ""
echo -e "${YELLOW}Next steps:${NC}"
echo "1. Configure SSL certificates for production use"
echo "2. Set up LDAP integration if needed"
echo "3. Configure additional cluster nodes"
echo "4. Review and adjust security settings"
Review the script before running. Execute with: bash install.sh