Keycloak Configuration with keycloak.conf: Examples & Precedence

Tested on Red Hat Enterprise Linux 10.2 (Coughlan)
Package Keycloak 26.7.3 ZIP from GitHub releases
Applies to Keycloak server configuration across ZIP/systemd and container deployments; the same underlying option names and precedence apply when platforms such as Helm or the Keycloak Operator generate server configuration, although their delivery mechanics differ
Privilege Normal user for show-config lab commands
Scope How Keycloak reads keycloak.conf, file location, CLI vs KC_* vs KeyStore naming, configuration precedence, ${…} interpolation, alternate config files, build-time vs runtime options, sensitive values, and verification with show-config — not deployment wiring (see install guides) or a full KC_* catalog (see the environment-variables guide).
Related guides Configure Keycloak with PostgreSQL
Short on time?Beta Get the gist of this 13-minute read in seconds, then ask follow-up questions

Keycloak configuration merges four sources in a fixed order. When the same option appears in more than one place, CLI flags beat environment variables, which beat keycloak.conf, which beat the Java KeyStore configuration source.

This guide answers:

  • Where is keycloak.conf?
  • What syntax does it use?
  • How do CLI flags, KC_* variables, and the file relate?
  • Which source wins when they conflict?
  • How do you reference environment variables inside the file?
  • How do you verify what Keycloak actually loaded?

Misnamed options and wrong precedence cause most “I set it but Keycloak ignored it” tickets. You will point the same option at Keycloak three ways, then read show-config to see the effective value and its source.


What is keycloak.conf?

keycloak.conf is Keycloak's default configuration file. It uses simple key=value entries with kebab-case keys such as db-url-host and metrics-enabled.

Keycloak does not read a random property list. It merges configuration from:

  • Command-line parameters
  • Environment variables (KC_*)
  • keycloak.conf (or a file from --config-file)
  • Java KeyStore (--config-keystore)
Configuration sources (highest priority first):

  ┌───────────────┐
  │   CLI flags   │  --metrics-enabled=true
  └──────┬────────┘
         │ wins over
  ┌──────▼────────┐
  │  Environment  │  KC_METRICS_ENABLED=false
  └───────┬───────┘
         │ wins over
  ┌──────▼────────┐
  │ keycloak.conf │  metrics-enabled=true
  └──────┬────────┘
         │ wins over
  ┌──────▼────────┐
  │    KeyStore   │  alias kc.db-password
  └───────────────┘

This page is not a full option catalog — see All configuration upstream. The differentiator here is understanding why Keycloak used one value when you set another.


Where is keycloak.conf located?

By default, Keycloak reads conf/keycloak.conf from its installation directory (KC_HOME).

Install method Default file location / delivery
Linux ZIP / systemd — Install Keycloak on Linux, Run Keycloak as a systemd service <KC_HOME>/conf/keycloak.conf; guides in this series use /opt/keycloak/conf/keycloak.conf
Docker / Podman / Compose — Install Keycloak with Docker, Podman, Docker Compose /opt/keycloak/conf/keycloak.conf inside the official image; mount a file or supply KC_* variables
Helm community chart — Install Keycloak with Helm Chart values usually generate CLI args, env vars, or volume configuration; mount keycloak.conf only when the chart exposes the appropriate volume controls
Keycloak Operator — Install Keycloak on Kubernetes Configure primarily through the Keycloak CR; do not edit the Pod's keycloak.conf directly

On a fresh install the file is mostly commented examples. Production teams usually uncomment or add only the keys they need and keep secrets out of Git.

To preview the stock template from the 26.7.3 distribution without starting the server:

bash
head -20 "$KC_HOME/conf/keycloak.conf"

Sample output from a fresh ZIP extract:

output
# Basic settings for running in production. Change accordingly before deploying the server.

# Database

# The database vendor.
#db=postgres

# The username of the database user.
#db-username=keycloak

# The password of the database user.
#db-password=password

# The full database JDBC URL. If not provided, a default URL is set based on the selected database vendor.
#db-url=jdbc:postgresql://localhost/keycloak

# Observability

# If the server should expose healthcheck endpoints.
#health-enabled=true

The stock file provides commented examples of common settings. It is not an exhaustive option list, and many of the same Keycloak options can instead be supplied through environment variables or CLI arguments.

NOTE
Using a container? In the official Keycloak image, KC_HOME is /opt/keycloak, so the default configuration file is /opt/keycloak/conf/keycloak.conf. Mount your file there or supply settings through KC_* environment variables. The container install guides linked above carry the mount syntax and start commands for each runtime.

Example keycloak.conf syntax

The block below shows typical key=value lines — not a complete production configuration. Database, TLS, proxy, hostname, and observability requirements depend on the deployment. The upstream All configuration page lists every supported key with build-time vs runtime markers; Configuring Keycloak covers formats and precedence in more detail upstream.

text
# Example server settings
hostname=auth.example.com

# Runtime database settings
db-url-host=keycloak-postgres
db-username=keycloak
db-password=${DB_PASSWORD}
db-url-database=keycloak

# Example TLS settings
https-certificate-file=/etc/keycloak/tls/server.crt.pem
https-certificate-key-file=/etc/keycloak/tls/server.key.pem

health-enabled=true
metrics-enabled=true

Each line uses key=value with kebab-case keys (db-url-host, not KC_DB_URL_HOST). Keycloak maps these to internal kc.* properties when it starts.

DB_PASSWORD in the example is an ordinary deployment environment variable referenced from the file through ${…} interpolation. By contrast, KC_DB_PASSWORD directly configures Keycloak's db-password option and participates as the higher-priority environment-variable configuration source — you do not need db-password=${KC_DB_PASSWORD} when you already set KC_DB_PASSWORD. The interpolation section below uses a non-KC_ variable for the same reason. Other ways to keep secrets out of Git are in the sensitive-configuration section below.


CLI vs environment variables vs keycloak.conf

The same logical option has a different spelling per source. Keycloak normalizes them before applying precedence.

Source Format Example (db-url-host)
Command line --<key>=<value> --db-url-host=mydb
keycloak.conf <key>=<value> db-url-host=mydb
Environment KC_<KEY> (uppercase, underscores) KC_DB_URL_HOST=mydb
Java KeyStore Alias kc.<key> storing the value alias kc.db-password

For ordinary options, Keycloak maps environment-variable names back by lowercasing them and converting _ to -. Keys containing other special characters can be ambiguous; for those cases, Keycloak also supports the KCKEY_* mechanism for explicitly identifying the intended configuration key. See Keycloak environment variables for that syntax in detail.


Keycloak configuration precedence

When the same option is set in more than one place, the earlier row wins.

Priority Source
1 Command-line parameters
2 Environment variables
3 conf/keycloak.conf (or file from --config-file)
4 Java KeyStore (--config-keystore)

Lab: watch precedence with show-config

The commands below use $KC_HOME/bin/kc.sh show-config with --config-file so the lab does not overwrite your real conf/keycloak.conf. Set KC_HOME to your Keycloak installation directory — for example /opt/keycloak on a typical Linux ZIP install.

Create an isolated lab file:

bash
export KC_HOME=/opt/keycloak
mkdir -p /tmp/keycloak-conf-lab
cat > /tmp/keycloak-conf-lab/keycloak.conf <<'EOF'
metrics-enabled=true
health-enabled=true
EOF

Print the resolved configuration from the lab file:

bash
"$KC_HOME/bin/kc.sh" --config-file=/tmp/keycloak-conf-lab/keycloak.conf show-config

Look for lines that end with (keycloak.conf) — those values came from your lab file:

output
Current Mode: production
Current Configuration:
	kc.health-enabled =  true (keycloak.conf)
	kc.metrics-enabled =  true (keycloak.conf)
	...

Set an environment variable that targets the same option. KC_METRICS_ENABLED outranks the file:

bash
KC_METRICS_ENABLED=false "$KC_HOME/bin/kc.sh" --config-file=/tmp/keycloak-conf-lab/keycloak.conf show-config

The metrics line should now show (ENV):

output
	kc.metrics-enabled =  false (ENV)

Pass a CLI flag to win over both file and environment:

bash
KC_METRICS_ENABLED=false "$KC_HOME/bin/kc.sh" --config-file=/tmp/keycloak-conf-lab/keycloak.conf show-config --metrics-enabled=true

The source column switches to (CLI):

output
	kc.metrics-enabled =  true (CLI)

That (CLI) / (ENV) / (keycloak.conf) suffix is what you want when debugging “I set it in the file but Keycloak ignored me.” These labels are from Keycloak 26.7.3 on the lab ZIP distribution.


Use environment variables inside keycloak.conf

Teams often ship one static keycloak.conf for every environment. Use ${ENV_VAR} placeholders so the file stays static while secrets and hostnames vary per deploy.

Syntax:

  • ${MY_DB_HOST} — resolves the environment variable
  • ${MY_DB_HOST:postgres} — uses postgres as a fallback when the environment variable is unavailable

Create a lab file that defaults metrics to true but reads LAB_METRICS when present:

bash
cat > /tmp/keycloak-conf-lab/keycloak-interp.conf <<'EOF'
metrics-enabled=${LAB_METRICS:true}
health-enabled=true
EOF

LAB_METRICS is an ordinary environment variable referenced by the file. It does not need the KC_ prefix because it is not being mapped directly to a Keycloak option.

Without LAB_METRICS, show-config resolves the fallback:

bash
"$KC_HOME/bin/kc.sh" --config-file=/tmp/keycloak-conf-lab/keycloak-interp.conf show-config
output
	kc.metrics-enabled =  true (keycloak.conf)

Set the variable in the shell environment to flip the value without editing the file:

bash
LAB_METRICS=false "$KC_HOME/bin/kc.sh" --config-file=/tmp/keycloak-conf-lab/keycloak-interp.conf show-config
output
	kc.metrics-enabled =  false (keycloak.conf)

Whether an unresolved or empty value makes Keycloak fail depends on validation for that particular setting — not on the interpolation syntax itself.

Passwords that contain $ characters need care inside file expressions. If a Keycloak option is supplied directly as an environment variable and its value contains $, use KCRAW_<KEY> instead of KC_<KEY> when you need the value preserved literally. KCRAW_* applies to the environment-variable configuration source; values placed directly in keycloak.conf follow the file's escaping and expression rules. Do not define both KC_<KEY> and KCRAW_<KEY> for the same option; Keycloak treats that as a configuration error. Day-to-day database passwords are usually set with KC_DB_PASSWORD or a KeyStore entry instead of a committed file line.


Use a custom Keycloak configuration file

The precedence lab above deliberately used --config-file so it did not overwrite your real conf/keycloak.conf. The same option is useful when your deployment stores configuration elsewhere.

By default Keycloak reads conf/keycloak.conf. Point at another path with --config-file (short -cf) before the subcommand:

bash
"$KC_HOME/bin/kc.sh" --config-file=/path/to/keycloak.conf start

Useful cases:

  • A platform-managed config path (for example a Secret or ConfigMap mounted at a non-default path in Kubernetes)
  • Testing a candidate configuration with show-config before a full start
  • An entrypoint that selects a config file by environment

When --config-file is set, Keycloak does not merge that path with the default keycloak.conf; it reads the file you named instead.


Build-time vs runtime configuration

Modern Keycloak is Quarkus-based. Keycloak distinguishes when an option is applied from where its value comes from. Build-time options are persisted when Keycloak performs augmentation, while runtime options are evaluated when the server starts. CLI, environment variables, keycloak.conf, and KeyStore are configuration sources; they do not determine whether an option itself is build-time or runtime.

Option type When it takes effect Examples
Build-time option During kc.sh build, or during automatic augmentation on a non-optimized startup db, some feature and provider options
Runtime option When Keycloak starts db-url-host, db-username, db-password, hostname, TLS certificate paths

Configuration source and option type are separate concepts. A build-time option can be supplied through the CLI, an environment variable, or keycloak.conf; using a different source does not turn it into a runtime option. Build options are persisted in plain text regardless of the configuration source.

db is a build option. In an optimized deployment, select the database vendor when building the Keycloak image — for example kc.sh build --db=postgres. By default, non-optimized start and start-dev perform the required build/augmentation automatically before startup. With a pre-built optimized server, start --optimized skips that work and expects persisted build options to already match.

At runtime, point JDBC settings at the live server:

  • db-url-host, db-username, and db-password in keycloak.conf
  • or the matching KC_DB_* environment variables

Do not put secrets into build — only non-sensitive choices like the database vendor. When a build-time option changes after an optimized build, Keycloak warns instead of silently switching — run kc.sh build again or align the flag with the last build. See Keycloak start vs start-dev for how non-optimized startup performs build/augmentation automatically.


Store sensitive Keycloak configuration

Keep secrets out of a committed keycloak.conf. Common runtime sources include:

  • Environment variables supplied securely by your deployment platform — syntax catalog in Keycloak environment variables
  • Java KeyStore configuration source (--config-keystore, --config-keystore-password)
  • A secret manager that injects env vars referenced from ${…} in the file

KeyStore aliases use the kc.<option> form and sit at the lowest precedence. Upstream guidance recommends the KeyStore primarily for sensitive runtime values while non-secret tuning stays in keycloak.conf.

Do not use a KeyStore to try to protect build options. Keycloak persists build options in plain text regardless of the configuration source, so reserve the KeyStore primarily for sensitive runtime configuration.


Why is Keycloak ignoring my configuration?

When a value does not match what you expect, work through this list before chasing unrelated logs.

Symptom Likely cause What to check
File value never appears Higher-priority source set the same key Run show-config; read the (CLI) / (ENV) suffix
Edit had no effect Wrong file or path Confirm $KC_HOME/conf/keycloak.conf or the path passed to --config-file
Runtime change ignored Option is build-time Re-run kc.sh build; look for the “build time options” warning with --optimized
--optimized startup warning Persisted build differs from CLI Rebuild or align the flag with the last build
Raw Quarkus property set but no effect Keycloak does not support or expose that underlying Quarkus property Prefer the corresponding first-class Keycloak option when one exists; Keycloak logs warnings for some unsupported or replaced Quarkus settings
Config looks right, behavior unchanged Server not restarted Restart the Keycloak process after changing file or environment
show-config shows file value but running server differs Started with different --config-file or environment Match flags and env vars used in show-config
Unknown option: '--config-file' on subcommand Flag placed after show-config / start Put --config-file=… immediately after kc.sh
${VAR} does not resolve Variable is missing or misspelled in the Keycloak process environment Export or inject the variable into the same environment that runs kc.sh
PostgreSQL URL wrong in file but vendor is H2 Optimized image built for wrong vendor Rebuild with --db=postgres, then set JDBC runtime keys
$ in a direct KC_* environment value is modified Expression processing of the environment value Use the corresponding KCRAW_* form when the value must be preserved literally
$ behaves unexpectedly inside keycloak.conf File expression or escaping rules Escape the value according to Keycloak configuration-file rules, or deliver the secret using an appropriate environment or KeyStore source

The show-config source column is the fastest proof for precedence problems.


Verify configuration with show-config

show-config prints the merged configuration without starting HTTP listeners. Filter mentally for the keys you care about, or pipe to grep:

bash
"$KC_HOME/bin/kc.sh" show-config 2>&1 | grep -i metrics
output
	kc.metrics-enabled =  true (keycloak.conf)

show-config is useful for confirming effective configuration and its source. Treat its output as potentially sensitive and avoid publishing unreviewed output from production systems. Run it after editing keycloak.conf, changing KC_* variables, or adding CLI flags — before you restart a production server.


Summary

Keycloak configuration is layered, not a single file. The same option appears as --db-url-host, db-url-host=… in keycloak.conf, and KC_DB_URL_HOST in the environment. Command-line parameters take precedence over environment variables, which take precedence over the configuration file, which take precedence over the Java KeyStore configuration source — and show-config prints which layer won.

Keep keycloak.conf focused on stable settings needed by your deployment instead of copying the entire upstream option catalog. The syntax example above shows hostname, database, TLS, health, and metrics as illustrations only. Use ${ENV:default} when one file must serve multiple environments without rebuilding. Separate build-time choices (--db=postgres) from runtime secrets (db-password or KC_DB_PASSWORD) so credentials never land in an optimized build artifact.

When a setting seems ignored, check precedence first, then the file path, then whether the option is build-time. Run show-config before a full restart to confirm the effective configuration.


References

  • Configuring Keycloak — official precedence, formats, and build optimization
  • All configuration — searchable option reference with build vs runtime markers
  • Running Keycloak in a container — image layout and conf/ paths

Frequently Asked Questions

Does keycloak.conf override environment variables?

No. Environment variables rank above the file. Only command-line parameters rank above environment variables. If both set metrics-enabled, the environment wins unless you also pass --metrics-enabled on the CLI.

Can I use keycloak.conf and KC_* together?

Yes, for different options. For the same option, one value wins by precedence — do not expect a merge. Use the file for stable structure and ${…} references; use KC_* for deploy-specific overrides.

Where do I put the database password?

Prefer KC_DB_PASSWORD or a KeyStore entry at runtime. Avoid committing db-password= in Git. The PostgreSQL lab article wires JDBC env vars end to end.

Do I need to run build after every keycloak.conf edit?

No. Editing a runtime option does not require another build; restart Keycloak so the new runtime value is loaded. Run kc.sh build again when you change a build-time option in an optimized deployment. The configuration source — file, environment, or CLI — does not by itself determine whether an option is build-time or runtime.

Finished reading?Beta Get the key takeaways in seconds, then ask follow-up questions
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with 15+ years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)