Keycloak
Overview
Keycloak is a leading open-source identity and access management platform. It provides single sign-on, social login, identity brokering, user federation, fine-grained authorization, OpenID Connect and SAML 2.0 for web, mobile and API workloads.
This SecureAnyCloud image runs Keycloak as a systemd service on OpenJDK 21 headless, backed by a local PostgreSQL 16 database and fronted by an nginx reverse proxy on TCP port 80.
What you get:
- Keycloak listening on
127.0.0.1:8080, with nginx forwarding browser and OIDC traffic to it. The proxy adds X-Forwarded headers and a websocket upgrade for the admin console event stream - A management interface for health and metrics on
127.0.0.1:9000, loopback only, with the/health/readyendpoint enabled - PostgreSQL storage for realm, client, user, group, role and session state, on a dedicated EBS data volume mounted at
/var/lib/postgresql - A unique administrator password generated on the first boot of every instance and set in Keycloak with the
kc.sh bootstrap-admin usercommand, which is Keycloak 26's replacement for the legacyKEYCLOAK_ADMINenvironment variables - The plain-text administrator password in
/root/keycloak-credentials.txtwith mode0600, readable only by root - A PostgreSQL
keycloakrole password, also rotated on first boot and written to a root-only environment file at/etc/keycloak/db-password.env
Two instances launched from the same Amazon Machine Image never share a password.
Prerequisites
Before you deploy this image, you need:
- An Amazon Web Services account where you can launch EC2 instances
- IAM permissions to launch instances, create security groups and subscribe to AWS Marketplace products
- An EC2 key pair in the target Region for SSH access to the instance
- A VPC and subnet in the target Region, with a security group that allows inbound port 22 from your management network and inbound port 80 from the trusted networks that will browse to Keycloak
- The AWS CLI (version 2) installed locally, if you plan to deploy from the command line
Launch the instance from the AWS Marketplace
Sign in to the AWS Management Console, open the EC2 service, and select Launch instance. Under Application and OS Images, choose AWS Marketplace AMIs and search for Keycloak. Select the SecureAnyCloud listing and choose Select, then Continue on the subscription summary.
Pick an instance type of m5.large or larger. Choose your EC2 key pair under Key pair (login). Under Network settings, select your VPC and subnet. Create or select a security group that allows inbound port 22 from your management network and inbound port 80 from the trusted networks that will reach Keycloak.
Do not open port 80 to the public internet until you have configured TLS (see Step 14). Select Launch instance. First-boot initialisation takes approximately two to three minutes after the instance state becomes Running and the status checks pass.
Launch the instance from the AWS CLI
The following command launches an instance from the SecureAnyCloud Keycloak Marketplace AMI into an existing subnet and security group.
Replace <ami-id> with the AMI ID shown on the Marketplace listing, <key-name> with your EC2 key pair name, and <subnet-id> with your subnet ID. Replace <security-group-id> with a security group that opens ports 22 and 80, as described above.
aws ec2 run-instances \
--image-id <ami-id> \
--instance-type m5.large \
--key-name <key-name> \
--subnet-id <subnet-id> \
--security-group-ids <security-group-id> \
--block-device-mappings '[{"DeviceName":"/dev/sda1","Ebs":{"VolumeSize":30,"VolumeType":"gp3"}}]' \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=keycloak-01}]'On success, the command prints a JSON document. Note the instance ID. Once the instance is running, retrieve its public address with aws ec2 describe-instances --instance-ids <instance-id> --query "Reservations[].Instances[].PublicIpAddress" --output text.
Connect to your instance
Connect over SSH with the key pair you selected and the public IP address from Step 2. The SSH login user depends on the operating system of the AMI variant you launched. For Keycloak 26 on Ubuntu 24.04, the login user is ubuntu.
The first-boot service runs after the SSH daemon becomes ready. The credentials file is in place within about two minutes of the instance entering the Running state. Wait for the file to appear before you sign in to the web interface.
ssh <login-user>@<public-ip>
sudo cat /root/keycloak-credentials.txtThe plain-text file contains the Keycloak URL, the administrator username (SecureAnyCloud) and the administrator password. Copy these values to a secure place, such as a password manager or an encrypted vault. Do not commit them to source control.
Each command block in this guide that calls the Keycloak HTTP API begins by reading the administrator password from the credentials file into a PASSWORD shell variable. This keeps every block self-contained:
PASSWORD="$(sudo awk -F= '/^KEYCLOAK_ADMIN_PASSWORD=/ {print $2}' /root/keycloak-credentials.txt)" echo "admin password length: ${#PASSWORD}"Verify that the server is healthy
Keycloak exposes an unauthenticated /health/ready page on its management interface. It confirms that the server, the JDBC pool and the cache are all healthy.
The OIDC token endpoint on the master realm proves that the administrator user can sign in:
PASSWORD="$(sudo awk -F= '/^KEYCLOAK_ADMIN_PASSWORD=/ {print $2}' /root/keycloak-credentials.txt)" curl -s -o /dev/null -w "health HTTP: %{http_code}\n" http://127.0.0.1:9000/health/ready
TOKEN=$(curl -s -m 10 \
-d "grant_type=password" -d "client_id=admin-cli" \
-d "username=sac" -d "password=${PASSWORD}" \
http://127.0.0.1:8080/realms/master/protocol/openid-connect/token | jq -r .access_token)
echo "token length: ${#TOKEN}"The first response should return health HTTP: 200. The second prints the length of the OIDC access token. A value above 500 confirms that the administrator credentials authenticated and that the master realm is serving tokens.
With the token, you can also call the admin REST API. The following query returns the realms configured on the server, which is initially just master:
PASSWORD="$(sudo awk -F= '/^KEYCLOAK_ADMIN_PASSWORD=/ {print $2}' /root/keycloak-credentials.txt)"
TOKEN=$(curl -s -m 10 -d "grant_type=password" -d "client_id=admin-cli" \
-d "username=sac" -d "password=${PASSWORD}" \
http://127.0.0.1:8080/realms/master/protocol/openid-connect/token | jq -r .access_token)
curl -s -H "Authorization: Bearer ${TOKEN}" \
http://127.0.0.1:8080/admin/realms | jq '[.[] | {realm, enabled}]'Review the server components
The image contains these components:
- Keycloak 26.x, from github.com/keycloak/keycloak/releases
- OpenJDK 21 headless, from Ubuntu noble main
- PostgreSQL 16, from apt.postgresql.org
- nginx 1.24, from Ubuntu noble main
- Ubuntu 24.04 LTS, the SecureAnyCloud golden base image
- SecureAnyCloud overrides: keycloak.conf and systemd drop-ins baked into the image
The image carries no second backend database, no message broker and no second exposed port. Keycloak's built-in user database and the local PostgreSQL backend handle authentication and persistence. The server stores all of its persistent state in the PostgreSQL keycloak database on the dedicated data volume.
Review the filesystem layout
The image places Keycloak under /opt/keycloak and the PostgreSQL data directory on the dedicated EBS volume mounted at /var/lib/postgresql. The main directories and files are:
/opt/keycloak/: the upstream Keycloak distribution/opt/keycloak/conf/keycloak.conf: Keycloak's Quarkus configuration (db, http, proxy, health)/opt/keycloak/bin/kc.sh: the Keycloak command-line interface (build,start,bootstrap-admin,export,import)/etc/systemd/system/keycloak.service: the Keycloak systemd unit/etc/systemd/system/keycloak.service.d/sac-hostname.conf: a drop-in that injects--hostname=<public-ip>at first boot/etc/nginx/sites-available/keycloak: the nginx reverse proxy configuration/var/lib/postgresql/: the PostgreSQL data directory on the dedicated data volume (database files and write-ahead log)/etc/keycloak/admin-password.txt: the per-instance admin password file, mode0640 root:keycloak/etc/keycloak/db-password.env: the per-instance PostgreSQLkeycloakrole password, mode0640 keycloak:keycloak/var/log/keycloak-firstboot.log: the first-boot rotation log/root/keycloak-credentials.txt: the per-instance administrator password, mode0600, root only
Start, stop and check status
Keycloak runs as the systemd service keycloak.service, backed by postgresql.service and fronted by nginx.service. The first-boot unit is keycloak-firstboot.service. It is skipped on every boot after the first because its sentinel lives at /var/lib/sac/data/keycloak-firstboot.done.
Manage the main services with systemctl:
sudo systemctl status --no-pager keycloak
sudo systemctl is-active postgresql
sudo systemctl is-active keycloak
sudo systemctl is-active nginxRestart the Keycloak server after you edit its configuration:
sudo systemctl restart keycloakKeycloak runs as the keycloak system user, owns /opt/keycloak and writes its log to the systemd journal. View the log with sudo journalctl -u keycloak -n 200 --no-pager.
Open the Keycloak admin console
nginx fronts Keycloak on TCP port 80. Open port 80 from your management network in the security group, then open this URL in your browser:
http://<public-ip>/Keycloak shows its sign-in page. Enter the administrator username SecureAnyCloud and the administrator password from /root/keycloak-credentials.txt.
After you sign in, Keycloak opens the admin console with the master realm selected. The sidebar covers realm management, clients, client scopes, realm roles, users, groups, sessions, events, realm settings, authentication flows, identity providers and user federation.
The Clients page lists every OpenID Connect and SAML client configured in the realm. A fresh master realm contains the built-in clients (account, account-console, admin-cli, broker, master-realm, security-admin-console). Your applications appear here once you register them.
The Users page lists the end users in the realm. A fresh master realm contains the SecureAnyCloud administrator that the first-boot service provisioned. Your end users appear here once you create them or an identity provider federates them in.
Keycloak exposes both a web interface and a REST API, so you can also do anything from the web interface with curl, the Keycloak CLI (kcadm.sh) or one of the language SDKs.
Create a realm from the API
Production deployments rarely use the master realm for application identities. Create a separate realm for each customer environment or tenant instead. The following block creates a realm named acme with the admin REST API:
PASSWORD="$(sudo awk -F= '/^KEYCLOAK_ADMIN_PASSWORD=/ {print $2}' /root/keycloak-credentials.txt)"
TOKEN=$(curl -s -m 10 -d "grant_type=password" -d "client_id=admin-cli" \
-d "username=sac" -d "password=${PASSWORD}" \
http://127.0.0.1:8080/realms/master/protocol/openid-connect/token | jq -r .access_token)
curl -s -o /dev/null -w "create realm HTTP: %{http_code}\n" \
-H "Authorization: Bearer ${TOKEN}" -H "Content-Type: application/json" \
-X POST http://127.0.0.1:8080/admin/realms \
-d '{"realm":"acme","enabled":true,"displayName":"Acme Corp"}'
curl -s -H "Authorization: Bearer ${TOKEN}" \
http://127.0.0.1:8080/admin/realms/acme | jq '{realm, enabled, displayName}'The first call creates the realm and returns 201 Created. The second confirms the realm exists and prints its metadata.
Create an OpenID Connect client
Register a client application in the acme realm so that it can authenticate end users with OIDC:
PASSWORD="$(sudo awk -F= '/^KEYCLOAK_ADMIN_PASSWORD=/ {print $2}' /root/keycloak-credentials.txt)"
TOKEN=$(curl -s -m 10 -d "grant_type=password" -d "client_id=admin-cli" \
-d "username=sac" -d "password=${PASSWORD}" \
http://127.0.0.1:8080/realms/master/protocol/openid-connect/token | jq -r .access_token)
curl -s -o /dev/null -w "create client HTTP: %{http_code}\n" \
-H "Authorization: Bearer ${TOKEN}" -H "Content-Type: application/json" \
-X POST http://127.0.0.1:8080/admin/realms/acme/clients \
-d '{"clientId":"web-portal","protocol":"openid-connect","publicClient":false,"standardFlowEnabled":true,"redirectUris":["https://web-portal.example.com/*"],"webOrigins":["https://web-portal.example.com"]}'
curl -s -H "Authorization: Bearer ${TOKEN}" \
"http://127.0.0.1:8080/admin/realms/acme/clients?clientId=web-portal" | jq '.[0] | {clientId, enabled, protocol}'The application receives an id_token and an access_token through the OAuth 2.0 authorization code flow. Replace the redirect URIs and web origins with the actual URLs of your application.
Create an end user
Add an end user to the acme realm. Replace the password value with one from your secrets manager:
PASSWORD="$(sudo awk -F= '/^KEYCLOAK_ADMIN_PASSWORD=/ {print $2}' /root/keycloak-credentials.txt)"
TOKEN=$(curl -s -m 10 -d "grant_type=password" -d "client_id=admin-cli" \
-d "username=sac" -d "password=${PASSWORD}" \
http://127.0.0.1:8080/realms/master/protocol/openid-connect/token | jq -r .access_token)
curl -s -o /dev/null -w "create user HTTP: %{http_code}\n" \
-H "Authorization: Bearer ${TOKEN}" -H "Content-Type: application/json" \
-X POST http://127.0.0.1:8080/admin/realms/acme/users \
-d '{"username":"alice","enabled":true,"email":"alice@example.com","firstName":"Alice","lastName":"Anderson","credentials":[{"type":"password","value":"change-me-in-your-secrets-manager","temporary":false}]}'
curl -s -H "Authorization: Bearer ${TOKEN}" \
"http://127.0.0.1:8080/admin/realms/acme/users?username=alice" | jq '.[0] | {username, email, enabled}'Rotate the administrator password
For a production deployment, rotate the administrator password that was generated on first boot. You can re-run the bootstrap procedure manually after stopping Keycloak, or use the admin REST API while Keycloak is running.
The REST API approach is recommended because it does not require a service restart:
PASSWORD="$(sudo awk -F= '/^KEYCLOAK_ADMIN_PASSWORD=/ {print $2}' /root/keycloak-credentials.txt)" NEW_PASSWORD="$(head -c 32 /dev/urandom | base64 | tr -d '/+=' | head -c 24)"
TOKEN=$(curl -s -m 10 -d "grant_type=password" -d "client_id=admin-cli" \
-d "username=sac" -d "password=${PASSWORD}" \
http://127.0.0.1:8080/realms/master/protocol/openid-connect/token | jq -r .access_token)
USER_ID=$(curl -s -H "Authorization: Bearer ${TOKEN}" \
"http://127.0.0.1:8080/admin/realms/master/users?username=sac" | jq -r '.[0].id')
curl -s -o /dev/null -w "reset HTTP: %{http_code}\n" \
-H "Authorization: Bearer ${TOKEN}" -H "Content-Type: application/json" \
-X PUT "http://127.0.0.1:8080/admin/realms/master/users/${USER_ID}/reset-password" \
-d "{\"type\":\"password\",\"value\":\"${NEW_PASSWORD}\",\"temporary\":false}" echo "$NEW_PASSWORD" | sudo tee /etc/keycloak/admin-password.txt > /dev/null
sudo chown root:keycloak /etc/keycloak/admin-password.txt
sudo chmod 0640 /etc/keycloak/admin-password.txt
sudo sed -i "s|^KEYCLOAK_ADMIN_PASSWORD=.*|KEYCLOAK_ADMIN_PASSWORD=$NEW_PASSWORD|" /root/keycloak-credentials.txtSign in again with the new password to confirm the rotation took effect. Store the new value in your secrets manager immediately.
Configure an identity provider
Keycloak can federate identities from external identity providers, including Google, Microsoft Entra ID, GitHub and any other OIDC or SAML 2.0 provider.
Open your realm in the admin console, select Identity providers in the left sidebar, and choose the provider type from the menu. Enter the provider's client ID and client secret, which you get from that provider's console. Keycloak generates a redirect URI. Paste it back into the provider's configuration.
For programmatic configuration, the /admin/realms/<realm>/identity-provider/instances endpoint creates and lists identity providers. A typical Google federation configuration is:
PASSWORD="$(sudo awk -F= '/^KEYCLOAK_ADMIN_PASSWORD=/ {print $2}' /root/keycloak-credentials.txt)"
TOKEN=$(curl -s -m 10 -d "grant_type=password" -d "client_id=admin-cli" \
-d "username=sac" -d "password=${PASSWORD}" \
http://127.0.0.1:8080/realms/master/protocol/openid-connect/token | jq -r .access_token)
curl -s -o /dev/null -w "create IdP HTTP: %{http_code}\n" \
-H "Authorization: Bearer ${TOKEN}" -H "Content-Type: application/json" \
-X POST http://127.0.0.1:8080/admin/realms/acme/identity-provider/instances \
-d '{"alias":"google","providerId":"google","enabled":true,"config":{"clientId":"<google-client-id>","clientSecret":"<google-client-secret>"}}'Once the provider is configured, the realm's sign-in page shows a Sign in with Google button below the username and password form.
Enable HTTPS with the reverse proxy
The image already provides nginx in front of Keycloak. For any deployment that carries customer traffic, terminate TLS in the nginx server block so that OIDC tokens and SAML assertions are not sent in clear text. nginx with a Let's Encrypt certificate gives you HTTPS without changing the Keycloak configuration.
The following commands assume that a DNS record points your fully qualified domain name at the instance's public IP address.
sudo apt-get update && sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d keycloak.your-domain.example \
--non-interactive --agree-tos -m you@your-domain.example \
--redirectcertbot rewrites /etc/nginx/sites-available/keycloak to listen on TCP 443 and redirect HTTP to HTTPS. Then update the Keycloak hostname so that issuer URIs and admin console URLs use the new domain:
sudo install -d -m 0755 /etc/systemd/system/keycloak.service.d
sudo tee /etc/systemd/system/keycloak.service.d/sac-hostname.conf > /dev/null <<'CONF' [Service] Environment=KC_HOSTNAME=keycloak.your-domain.example ExecStart= ExecStart=/opt/keycloak/bin/kc.sh start --optimized --hostname=https://keycloak.your-domain.example --hostname-strict=true CONF
sudo systemctl daemon-reload
sudo systemctl restart keycloakCustomer applications and identity providers then connect to https://keycloak.your-domain.example, and the proxy talks to Keycloak on localhost. In the security group, restrict port 80 to redirect-only traffic from the public internet. Restrict port 443 to the trusted networks that need to reach Keycloak.
Back up and maintain the server
Keycloak stores all of its persistent state in the PostgreSQL keycloak database on the dedicated /var/lib/postgresql data volume. For a point-in-time consistent backup, run pg_dump against the running database. For very large deployments, take an Amazon EBS snapshot of the data volume instead.
sudo -u postgres pg_dump -Fc keycloak \
-f /var/backups/keycloak-$(date +%F).pgdumpShip the dump to an Amazon S3 bucket or another object store. Restore a custom-format (-Fc) dump with pg_restore -d keycloak -c /var/backups/keycloak-<date>.pgdump.
For larger deployments, snapshot the /var/lib/postgresql EBS volume instead. A snapshot is faster than pg_dump and does not lock the database.
Ubuntu's unattended-upgrades is enabled by default, so kernel and package security patches apply automatically. To update Keycloak itself, replace the contents of /opt/keycloak with the new release's tarball. Then run sudo -u keycloak /opt/keycloak/bin/kc.sh build --db=postgres and restart keycloak.service. Read the Keycloak release notes before you apply a major version jump.
Scale beyond a single instance
This image is a single-instance Keycloak deployment. For larger deployments:
- Grow the
/var/lib/postgresqlEBS volume and extend the filesystem when the realm and session tables outgrow the initial disk. - Move PostgreSQL to Amazon RDS for managed backups, point-in-time recovery and a multi-AZ deployment. Update
db-urlin/opt/keycloak/conf/keycloak.confto match. - Put Keycloak behind an Application Load Balancer with TLS termination, and restrict port 80 in the security group to the load balancer and the management network.
- Run multiple Keycloak instances against the same database. Keycloak forms an Infinispan cluster automatically, so session state is shared across the cluster.
- Take regular snapshots of
/var/lib/postgresqland document the restore drill in your runbook.
The official Keycloak documentation at https://www.keycloak.org/documentation covers cluster setup, the operator for Kubernetes, and realm export and import in detail.
Security recommendations
Keycloak is a sensitive identity and access management server. It issues the OIDC tokens and SAML assertions that your applications trust. Apply the following before you take customer traffic:
- Never expose port 80 to the public internet without TLS. Configure HTTPS with Let's Encrypt (Step 14) and restrict port 80 to redirect-only.
- Rotate the administrator password as described in Step 12, and store the new value in your secrets manager.
- Use individual administrator accounts. Create one for every administrator and stop using the bootstrap SecureAnyCloud account for routine work. Assign the realm-admin role to those accounts under Role mapping.
- Restrict the security group so that port 80 and port 443 are reachable only from your trusted networks and identity provider callbacks.
- Use a separate realm per environment (production, staging) and per tenant. The master realm is for server administration only.
- Keep Keycloak up to date. Identity providers are a high-value attack target. Subscribe to the Keycloak security advisory list and apply patches promptly.
- Back up the PostgreSQL
keycloakdatabase regularly and test restores.
Screenshots
The Keycloak sign-in page, served behind nginx on port 80 and ready for the per-instance SecureAnyCloud administrator credentials.
The Keycloak master realm overview after sign-in, showing realm settings, clients, users, groups and identity providers.
The Keycloak Clients list, showing the OpenID Connect and SAML applications registered in the realm and their endpoints.
The Keycloak Users page, where administrators create, browse and manage end users and their credentials inside a realm.
Support
SecureAnyCloud provides 24/7/365 expert technical support for this image. Guaranteed response within 24 hours, one hour average for critical issues. Contact sales@secureanycloud.com.
For general Keycloak questions, realm modelling and identity federation, consult the official documentation at https://www.keycloak.org/documentation and the Keycloak community at https://www.keycloak.org/community.
Deploy on AWS
Launch Keycloak on AWS with 24/7 support from SecureAnyCloud.
View on Marketplace
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com