| Tested on | Red Hat Enterprise Linux 10.2 (Coughlan) |
|---|---|
| Package | podman-5.8.2-5.el10_2.x86_64quay.io/keycloak/keycloak:26.7.3openssl-3.5.5-6.el10_2.x86_64 |
| Applies to | Any Linux host running the official Quay image or ZIP distribution — HTTPS options are the same whether you use kc.sh or container KC_* variables |
| Privilege | Normal user for rootless Podman lab commands |
| Scope | Direct HTTPS on Keycloak (8443) with PEM or PKCS12/JKS, hostname URL alignment, certificate reload, TLS protocol and port options, and HTTPS verification. |
| Related guides | Configure Keycloak behind a reverse proxy Keycloak keycloak.conf guide Keycloak hostname |
In this article we will configure Keycloak HTTPS/TLS with PEM or PKCS12 certificates on Keycloak 26.7.3.
OIDC clients and browsers care about the URL scheme, hostname, and port Keycloak advertises. The steps below show the universal kc.sh options first, then a tested Podman lab that sets the same values with KC_* environment variables. If TLS terminates at a reverse proxy instead, Keycloak often runs with KC_HTTP_ENABLED=true and KC_PROXY_HEADERS; use the reverse-proxy sibling in Related guides for that layout.
What this TLS lab teaches
This guide focuses on HTTPS configured on Keycloak itself.
At the end you will understand:
- How to enable HTTPS with PEM files or a PKCS12/JKS keystore
- How to use a full
KC_HOSTNAMEURL when Keycloak must advertise a specific public scheme or port - How certificate reload works without restarting Keycloak
- How to change the HTTPS port and restrict TLS protocols
Direct TLS on Keycloak
┌────────┐ TLS ┌───────────────┐
│ Client │ ─────► │ Keycloak :8443│ KC_HTTPS_* + KC_HOSTNAME (full URL)
└────────┘ └───────────────┘| Purpose | CLI | Environment |
|---|---|---|
| Certificate (PEM) | --https-certificate-file |
KC_HTTPS_CERTIFICATE_FILE |
| Private key (PEM) | --https-certificate-key-file |
KC_HTTPS_CERTIFICATE_KEY_FILE |
| Keystore | --https-key-store-file |
KC_HTTPS_KEY_STORE_FILE |
| Keystore password | --https-key-store-password |
KC_HTTPS_KEY_STORE_PASSWORD |
| HTTPS port | --https-port |
KC_HTTPS_PORT |
| TLS protocols | --https-protocols |
KC_HTTPS_PROTOCOLS |
| Certificate reload period | --https-certificates-reload-period |
KC_HTTPS_CERTIFICATES_RELOAD_PERIOD |
Generate a lab PEM certificate
Create a self-signed certificate whose Subject Alternative Name (SAN) matches the hostname clients will use. These steps extend the base layout from Install Keycloak with Podman.
mkdir -p /tmp/kc-tls-lab/conf
openssl req -x509 -newkey rsa:2048 -nodes \
-keyout /tmp/kc-tls-lab/conf/server.key.pem \
-out /tmp/kc-tls-lab/conf/server.crt.pem \
-days 365 \
-subj '/CN=auth.lab.local' \
-addext 'subjectAltName=DNS:auth.lab.local,DNS:localhost'The Keycloak process must read both files. If the private key is mode 600 owned by another user, startup fails with AccessDeniedException. For this temporary lab the files are made readable by the container user. Do not make a production private key world-readable; instead set ownership, group, or ACLs so only the Keycloak process can read it:
chmod 644 /tmp/kc-tls-lab/conf/server.crt.pem /tmp/kc-tls-lab/conf/server.key.pemConfigure Keycloak HTTPS with a PEM certificate
On a ZIP or package install, start Keycloak with the certificate paths and a full public hostname URL:
bin/kc.sh start \
--hostname=https://auth.example.com \
--https-certificate-file=/path/to/server.crt.pem \
--https-certificate-key-file=/path/to/server.key.pemThe equivalent container configuration uses KC_HTTPS_CERTIFICATE_FILE and KC_HTTPS_CERTIFICATE_KEY_FILE.
Podman (or Docker) port publishing maps container 8443 to host 18443, but that mapping does not change the public URL Keycloak advertises. When clients reach Keycloak on a non-default port, fix the scheme and port in KC_HOSTNAME:
podman run -d --name kc-tls-direct --replace \
-v /tmp/kc-tls-lab/conf:/opt/keycloak/conf:Z \
-p 127.0.0.1:18443:8443 \
-e KC_BOOTSTRAP_ADMIN_USERNAME=admin \
-e KC_BOOTSTRAP_ADMIN_PASSWORD=admin \
-e KC_HOSTNAME=https://auth.lab.local:18443 \
-e KC_HTTPS_CERTIFICATE_FILE=/opt/keycloak/conf/server.crt.pem \
-e KC_HTTPS_CERTIFICATE_KEY_FILE=/opt/keycloak/conf/server.key.pem \
-e KC_HTTP_ENABLED=false \
quay.io/keycloak/keycloak:26.7.3 startstart disables HTTP by default, so you must configure HTTPS on Keycloak for this lab. The same PEM paths work in keycloak.conf as https-certificate-file and https-certificate-key-file — see the keycloak.conf sibling under Related guides for file precedence.
First boot can take 30–90 seconds. When logs show https://0.0.0.0:8443, probe the published port (use curl --noproxy '*' if your shell sets https_proxy):
curl --noproxy '*' -sk -o /dev/null -w '%{http_code}\n' https://127.0.0.1:18443/An HTTP 200 or redirect (302) confirms TLS is listening.
Configure Keycloak HTTPS with a PKCS12 or JKS keystore
If both PEM certificate options and a keystore are configured, Keycloak uses the PEM certificate/key configuration.
Some teams ship a PKCS12 (.p12) or JKS keystore instead of separate PEM files:
bin/kc.sh start \
--hostname=https://auth.example.com \
--https-key-store-file=/path/to/keystore.p12 \
--https-key-store-password=changeit \
--https-key-store-type=PKCS12| Variable | Example |
|---|---|
KC_HTTPS_KEY_STORE_FILE |
/opt/keycloak/conf/keystore.p12 |
KC_HTTPS_KEY_STORE_PASSWORD |
changeit |
KC_HTTPS_KEY_STORE_TYPE |
PKCS12 (or JKS) |
Build a PKCS12 file from the lab PEM material:
openssl pkcs12 -export \
-in /tmp/kc-tls-lab/conf/server.crt.pem \
-inkey /tmp/kc-tls-lab/conf/server.key.pem \
-out /tmp/kc-tls-lab/conf/keystore.p12 \
-name keycloak \
-passout pass:changeitA PKCS12 file contains private key material. For this temporary lab make it readable by the container user the same way as the PEM key. Do not leave a production keystore world-readable:
chmod 644 /tmp/kc-tls-lab/conf/keystore.p12Start with the keystore instead of PEM paths. Use a different host port so this container does not collide with the PEM example:
podman run -d --name kc-tls-keystore --replace \
-v /tmp/kc-tls-lab/conf:/opt/keycloak/conf:Z \
-p 127.0.0.1:18444:8443 \
-e KC_BOOTSTRAP_ADMIN_USERNAME=admin \
-e KC_BOOTSTRAP_ADMIN_PASSWORD=admin \
-e KC_HOSTNAME=https://auth.lab.local:18444 \
-e KC_HTTPS_KEY_STORE_FILE=/opt/keycloak/conf/keystore.p12 \
-e KC_HTTPS_KEY_STORE_PASSWORD=changeit \
-e KC_HTTPS_KEY_STORE_TYPE=PKCS12 \
quay.io/keycloak/keycloak:26.7.3 startAfter startup, curl --noproxy '*' -sk against https://127.0.0.1:18444/ should return HTTP 200. Use PKCS12 when your CA or platform already distributes a single keystore bundle; PEM files are simpler for Kubernetes Secrets split into tls.crt and tls.key.
Hostname and public URLs
Certificate identity and Keycloak's advertised frontend URL must describe a URL clients can actually reach.
| Piece | Must align |
|---|---|
| Certificate SAN | Host clients type in the browser (auth.lab.local, auth.example.com) |
KC_HOSTNAME |
Full public URL when scheme or port is non-default (https://auth.lab.local:18443) |
OIDC issuer |
https://<hostname>[:port]/realms/<realm> |
Prefer a full URL when the externally visible scheme or port must stay fixed:
KC_HOSTNAME=https://auth.example.com
KC_HOSTNAME=https://auth.lab.local:18443With KC_HOSTNAME=https://auth.lab.local:18443 and port 18443 published, fetch discovery using --resolve so the host header matches the certificate:
curl --noproxy '*' -sk --resolve auth.lab.local:18443:127.0.0.1 \
https://auth.lab.local:18443/realms/master/.well-known/openid-configurationThe issuer field should match that public URL:
"issuer":"https://auth.lab.local:18443/realms/master"A certificate for auth.example.com combined with KC_HOSTNAME=https://auth.lab.local:18443 produces browser warnings and wrong redirect URLs. Hostname v2 rules, admin URL split, and strict mode are covered in the hostname sibling under Related guides.
Production start with HTTP disabled and no certificate paths fails fast — run it once without TLS to see the message:
podman run --rm -e KC_HTTP_ENABLED=false quay.io/keycloak/keycloak:26.7.3 startKey material not provided to setup HTTPS. Please configure your keys/certificates, or if HTTPS access is not needed see the `http-enabled` option. If you meant to start the server in development mode, see the `start-dev` command.That failure is why production start needs hostname and TLS planning up front, as covered in Keycloak start vs start-dev.
Change the HTTPS port and TLS protocols
Keycloak listens for HTTPS on port 8443 by default. Change the listener with:
bin/kc.sh start --https-port=9443 ...Or in a container:
-e KC_HTTPS_PORT=9443Remember that KC_HOSTNAME advertises the public URL. If clients still reach Keycloak through a published host port or a reverse proxy on 443, the hostname URL must reflect that public address — not only the container listen port.
By default Keycloak enables modern TLS protocols and does not turn on deprecated ones. To restrict the list explicitly:
-e KC_HTTPS_PROTOCOLS=TLSv1.3Or allow both current defaults:
-e KC_HTTPS_PROTOCOLS=TLSv1.3,TLSv1.2Reload TLS certificates without restarting Keycloak
Keycloak automatically reloads certificate, key, and keystore material configured through the https-* options. The default reload period is one hour.
Override it with https-certificates-reload-period or the environment form:
-e KC_HTTPS_CERTIFICATES_RELOAD_PERIOD=10mUse a supported duration greater than 30 seconds, such as 10m or 1h. Set -1 to disable automatic reload. After you replace the PEM files or keystore on the mounted volume, wait for the next reload window (or restart) before expecting clients to see the new certificate.
HTTPS and management ports
| Port | Purpose | Reverse-proxy this port? |
|---|---|---|
8443 (or 8080 when HTTP enabled) |
Admin Console, Account Console, OIDC/SAML endpoints, Admin REST API | Yes — application front door |
9000 |
Management — health, metrics, optional management TLS | No — keep internal |
HTTPS application traffic uses 8443 by default. Port 9000 is the separate management interface used when features such as health or metrics are enabled. When management TLS is not configured separately, its settings can inherit from the main HTTP server. Enabling health checks and probing /health/ready belongs in the dedicated health guide when published — this page stays on certificate configuration.
Verify TLS correctly
Walk through these checks after any HTTPS change.
Certificate and SAN
Pipe openssl s_client into x509 to print the subject and SAN extension for the name clients will use:
echo | openssl s_client -connect 127.0.0.1:18443 -servername auth.lab.local 2>/dev/null \
| openssl x509 -noout -subject -ext subjectAltNameConfirm DNS:auth.lab.local (or your real FQDN) appears in the SAN list.
HTTPS response on the application port
Send a HEAD request to the published HTTPS port and confirm TLS completes:
curl --noproxy '*' -sk -I https://127.0.0.1:18443/Expect HTTP/1.1 200 or 302 — not connection refused or TLS handshake errors.
OIDC discovery issuer
Fetch discovery JSON with --resolve so the hostname matches the certificate, then grep the issuer:
curl --noproxy '*' -sk --resolve auth.lab.local:18443:127.0.0.1 \
https://auth.lab.local:18443/realms/master/.well-known/openid-configuration \
| grep -o '"issuer":"[^"]*"'The issuer must match what client applications configure — scheme, host, and port included.
Common problems
| Symptom | Likely cause | Fix |
|---|---|---|
AccessDeniedException on server.key.pem or keystore.p12 |
Key file not readable by Keycloak | Make the file readable by the Keycloak user (lab: chmod 644; production: ownership/group/ACLs, not world-readable keys) |
Production start exits immediately |
No TLS material and HTTP disabled | Add KC_HTTPS_* certificate or keystore options |
| Browser certificate warning | Self-signed lab cert or SAN mismatch | Use a CA-signed cert; match SAN to the host in KC_HOSTNAME |
| Issuer missing published host port | KC_HOSTNAME set to hostname only while clients use a mapped port |
Set KC_HOSTNAME to the full URL (https://auth.lab.local:18443) |
| Admin Console redirect loop | Hostname does not match public URL | Align KC_HOSTNAME with the URL clients reach |
| OIDC issuer shows internal host:port | KC_HOSTNAME wrong or incomplete |
Fix the full hostname URL; re-check discovery JSON |
Proxy-related symptoms such as 403 Forbidden, redirects to http://, or wrong issuer behind Nginx belong in the reverse-proxy guide linked under Related guides.
Summary
Direct Keycloak HTTPS puts certificates on Keycloak itself (--https-certificate-file / KC_HTTPS_CERTIFICATE_FILE, or a keystore) and exposes the application on port 8443. Set KC_HOSTNAME to the full public URL when the scheme or port clients use differs from Keycloak's internal listener. Certificate identity, KC_HOSTNAME, and the OIDC issuer URL must agree; openssl s_client and the discovery document are quick proofs after every change.
Certificate reload defaults to one hour and can be shortened with KC_HTTPS_CERTIFICATES_RELOAD_PERIOD. Management traffic on port 9000 stays separate from user-facing HTTPS. For edge termination, re-encrypt, passthrough, and Nginx or HAProxy configs, continue with the reverse-proxy sibling.
References
- Configuring TLS — PEM, keystore, TLS protocols, certificate reload, and port
8443 - Configuring the hostname (v2) — full URL hostname, scheme and port for public URLs
- Configuring the management interface — port
9000and TLS inheritance
Frequently Asked Questions
What port does Keycloak HTTPS use?
The default HTTPS application port is 8443. Change it with --https-port or KC_HTTPS_PORT. Management health and metrics use port 9000 on a separate interface when those features are enabled.
Can I use PEM and a keystore at the same time?
If both are configured, Keycloak uses the PEM certificate and key. Configure only one style unless you intentionally want PEM to win.
Why does OIDC discovery show the wrong port?
The issuer URL follows KC_HOSTNAME. When clients reach Keycloak on a published host port that differs from container 8443, set KC_HOSTNAME to the full URL including that port.
Should I put Keycloak behind a reverse proxy instead?
Often yes in production. Direct HTTPS is still useful for labs, internal services, and re-encrypt or passthrough backends. When the proxy terminates TLS for clients, configure KC_HTTP_ENABLED and KC_PROXY_HEADERS on Keycloak and put Nginx or HAProxy in front.

