How to Configure Keycloak with Environment Variables (KC_*)

Tested on Red Hat Enterprise Linux 10.2 (Coughlan)
Package podman-5.8.2-5.el10_2.x86_64
quay.io/keycloak/keycloak:26.7.3
Applies to Any Linux host with a Keycloak ZIP install, Podman, or Docker
Privilege Normal user for rootless Podman and ZIP show-config commands
Scope Map Keycloak options to KC_* environment variables, pass them with Docker, Podman, Compose, or a Linux ZIP install, and diagnose ignored values. Does not catalog every option or deploy PostgreSQL.
Related guides Install Keycloak with Podman
Keycloak start vs start-dev
Install Keycloak with Docker
Keycloak container volumes and persistence
Install Keycloak on Kubernetes
Short on time?Beta Get the gist of this 17-minute read in seconds, then ask follow-up questions

In this article we will configure Keycloak using KC_* environment variables. Keycloak environment-variable names normally begin with KC_. The * in KC_* is only shorthand for any Keycloak variable; you do not type the asterisk.

An environment variable is a NAME=value setting available to a running process. When Keycloak starts with KC_HOSTNAME=auth.example.com in its environment, Keycloak reads that value as its hostname. Environment variables are especially convenient for containers because the same image can be configured differently without editing files inside it.


How Keycloak environment variables work

Keycloak server settings can be supplied as command-line options, environment variables, or lines in conf/keycloak.conf. Those three forms are different spellings of the same underlying option.

One setting, three configuration methods

Suppose you want to set Keycloak's hostname to auth.example.com. These three lines do the same job:

Same setting, three spellings:

  CLI                 --hostname=auth.example.com
  keycloak.conf       hostname=auth.example.com
  environment         KC_HOSTNAME=auth.example.com

Official Keycloak documentation uses the same idea: CLI, environment, and the configuration file are different representations of one server option.

How Keycloak option names map to KC_*

For normal Keycloak options, the environment variable follows a simple rule. Start from the option name, which normally uses lowercase words separated by hyphens (db-url-host, sometimes called kebab-case):

  • Uppercase it: DB-URL-HOST
  • Replace hyphens with underscores: DB_URL_HOST
  • Prefix KC_: KC_DB_URL_HOST
Same setting, three spellings:

  CLI                 --db-url-host=postgres
  keycloak.conf       db-url-host=postgres
  environment         KC_DB_URL_HOST=postgres
CLI / keycloak.conf Environment variable
--hostname=auth.example.com KC_HOSTNAME=auth.example.com
--db-url-host=mydb KC_DB_URL_HOST=mydb
--metrics-enabled=true KC_METRICS_ENABLED=true
--bootstrap-admin-username=admin KC_BOOTSTRAP_ADMIN_USERNAME=admin

Keys containing characters that do not map cleanly to this pattern can require special handling. That case is covered later under KCKEY_*. Most options use the normal KC_* form.

How to find the correct environment variable for a Keycloak option

The safest method is to look the option up rather than guessing the name.

Keycloak's All configuration page lists CLI and environment forms for each supported option. Search for the hyphenated option name (hostname, db-url-host, http-relative-path) and read the Env column on that row.

For ordinary options you can usually derive the name yourself with the conversion rule above. Confirm the result against All configuration when the value seems ignored.

The naming rule converts an existing Keycloak option into environment-variable form. It does not mean you can invent a KC_* variable by guessing its name. If the page has no matching option, the environment variable is not a Keycloak setting, even when the name looks logical.


Set Keycloak environment variables

Keycloak reads KC_* from the environment of the process that starts the server. Containers do not inherit variables from your host shell automatically. You pass them in with -e, --env-file, or a Compose environment: block.

Set variables with Docker or Podman -e

In Podman and Docker, -e NAME=value passes an environment variable into the container. --rm removes the container when the command finishes.

export KC_HOSTNAME=… on the host does not enter the container. The -e flag is what copies the value across:

Host shell                         Container
┌──────────────────────────┐
│ export KC_HOSTNAME=...   │
│                          │
│  not inherited ──✕──►    │   (missing unless you pass -e)
└──────────────────────────┘

Your terminal
    │
    │  -e KC_HOSTNAME=auth.example.com
    ▼
Container environment
    │
    │  KC_HOSTNAME=auth.example.com
    ▼
Keycloak process
    │
    ▼
hostname = auth.example.com

show-config asks Keycloak to print the final values it resolved after reading those sources. It does not start the Keycloak server or prove that an external service such as PostgreSQL is reachable. Here it only checks that Keycloak recognized the environment variable.

Pass one hostname and print what Keycloak resolved. Extra podman run flags such as publish and detach are covered in Run containers with podman run:

bash
podman run --rm -e KC_HOSTNAME=auth.example.com quay.io/keycloak/keycloak:26.7.3 show-config

The (ENV) suffix on the hostname line confirms the container environment supplied the value:

output
	kc.hostname =  auth.example.com (ENV)

docker run uses the same -e flags and the same KC_* names.

Set multiple variables with --env-file

An env file avoids repeating many -e flags and prevents their values from appearing directly in the command stored in your shell history. It is not a secure secret store.

This file is read by Docker or Podman. It is not a shell script, which is why you write KC_HOSTNAME=value, not export KC_HOSTNAME=value.

Create a file of KEY=value lines:

bash
cat > keycloak.env << 'EOF'
KC_HOSTNAME=auth.example.com
KC_HEALTH_ENABLED=true
KC_METRICS_ENABLED=true
EOF

cat prints nothing when the file is written. Load that file into the container:

bash
podman run --rm --env-file keycloak.env quay.io/keycloak/keycloak:26.7.3 show-config

All three names resolve from the environment:

output
	kc.health-enabled =  true (ENV)
	kc.metrics-enabled =  true (ENV)
	kc.hostname =  auth.example.com (ENV)

docker run --env-file keycloak.env is the same file format. Environment variables still appear in podman inspect / docker inspect. For production secrets, prefer the runtime secret mechanism your orchestrator provides.

Set variables with Docker Compose

Compose maps the same names under environment:. Quoting "true" keeps the Compose value clearly represented as the string passed to Keycloak rather than a YAML boolean:

yaml
services:
  keycloak:
    image: quay.io/keycloak/keycloak:26.7.3
    environment:
      KC_BOOTSTRAP_ADMIN_USERNAME: admin
      KC_BOOTSTRAP_ADMIN_PASSWORD: change_me
      KC_HEALTH_ENABLED: "true"
    command: start-dev

That block only shows how Compose spells KC_* variables. A full stack with PostgreSQL, networks, volumes, and health checks belongs in Keycloak Docker Compose with PostgreSQL.

Set variables on a Linux ZIP installation

Keycloak reads KC_* variables from the environment of the process that starts kc.sh. You do not need Docker or Podman to use them.

export makes the variable available to commands started from the current shell, including kc.sh. From the Keycloak install directory used in Install Keycloak on Linux, export the name in the same shell that will run the server, then confirm it resolves:

bash
export KC_HOSTNAME=auth.example.com
bin/kc.sh show-config
output
	kc.hostname =  auth.example.com (ENV)

The hostname line shows (ENV), so the exported variable reached kc.sh. Use bin/kc.sh start from that same shell when you want the server running. An export in one terminal is not inherited by systemd, sudo, or a second shell.


Verify environment variables with show-config

Keycloak may receive configuration from more than one place. show-config prints the resolved configuration: the final value for each option and where that value came from.

Read each line in three pieces:

text
kc.hostname =  auth.example.com (ENV)
     ↑                 ↑          ↑
 internal name    resolved     source
                  value
  • kc.hostname is Keycloak's internal representation of the option
  • auth.example.com is the value Keycloak will use
  • (ENV) means an environment variable supplied it

You will also see these source labels:

Suffix Meaning
(ENV) Environment variable
(CLI) Command-line flag
(Persisted) Value saved during kc.sh build
(keycloak.conf) or similar file source A configuration file

Sensitive values such as KC_DB_PASSWORD print as *******. The line still confirms the variable is set and came from (ENV).

Use show-config with the same -e / --env-file arguments you plan to use for start. It still does not start the server, open a port, or test that PostgreSQL is reachable.


Keycloak configuration precedence

You can configure the same setting in more than one place, especially when a container image already contains keycloak.conf and your deployment adds environment variables. When that happens, Keycloak must decide which value wins.

CLI vs environment vs keycloak.conf

Both environment variables and keycloak.conf set the same underlying kc.* properties. They differ in precedence, not in meaning. Highest source wins:

CLI
 ↓ wins over
Environment
 ↓ wins over
keycloak.conf
 ↓ wins over
Java KeyStore secrets (advanced configuration source)

File paths, ${…} interpolation in keycloak.conf, and KeyStore wiring are covered in the Keycloak keycloak.conf guide.

Example: what happens when the same option is set twice?

Suppose metrics-enabled is configured in three places:

  • keycloak.conf says false
  • KC_METRICS_ENABLED=true
  • The CLI says --metrics-enabled=false

Keycloak uses the CLI value (false). If you remove the flag, the environment value (true) wins over the file.

A live check of CLI versus environment looks like this. Metrics are false in the environment and true on the CLI:

bash
podman run --rm -e KC_METRICS_ENABLED=false \
  quay.io/keycloak/keycloak:26.7.3 show-config --metrics-enabled=true

The (CLI) suffix shows that the command-line value won:

output
	kc.metrics-enabled =  true (CLI)

Remove the flag or align the values when you want the environment variable to apply.


Common Keycloak environment variables

You do not need to memorize every Keycloak environment variable. The following examples show four common jobs: creating the first administrator, connecting a database, setting Keycloak's public URL, and enabling health or metrics endpoints.

Task Variable
Bootstrap admin username KC_BOOTSTRAP_ADMIN_USERNAME
Bootstrap admin password KC_BOOTSTRAP_ADMIN_PASSWORD
Database vendor KC_DB
Complete JDBC URL KC_DB_URL
Database username KC_DB_USERNAME
Database password KC_DB_PASSWORD
Public hostname or URL KC_HOSTNAME
HTTP listener KC_HTTP_ENABLED
Health endpoints KC_HEALTH_ENABLED
Metrics endpoints KC_METRICS_ENABLED

These are syntax examples. Dedicated articles cover the full database, TLS, and bootstrap procedures.

Bootstrap administrator

On first startup Keycloak can create the initial admin user from the environment:

Variable Example
KC_BOOTSTRAP_ADMIN_USERNAME admin
KC_BOOTSTRAP_ADMIN_PASSWORD ChangeMe!2026

Older Keycloak releases commonly used KEYCLOAK_ADMIN and KEYCLOAK_ADMIN_PASSWORD. In Keycloak 26, use KC_BOOTSTRAP_ADMIN_USERNAME and KC_BOOTSTRAP_ADMIN_PASSWORD.

These variables bootstrap the initial administrator. They do not reset an existing administrator on every restart. First-start methods and recovery after the first start are covered in Keycloak bootstrap admin.

PostgreSQL database

This does not connect to PostgreSQL. The command below uses show-config only to demonstrate how the database variables are recognized. A working server still needs an actual PostgreSQL instance reachable at the configured host.

Variable What it tells Keycloak
KC_DB Database type or vendor
KC_DB_URL_HOST PostgreSQL server hostname
KC_DB_URL_DATABASE Database name
KC_DB_USERNAME Database login username
KC_DB_PASSWORD Database login password
bash
podman run --rm \
  -e KC_DB=postgres \
  -e KC_DB_URL_HOST=postgres \
  -e KC_DB_URL_DATABASE=keycloak \
  -e KC_DB_USERNAME=keycloak \
  -e KC_DB_PASSWORD='KcDb!2026' \
  quay.io/keycloak/keycloak:26.7.3 show-config

show-config reports the vendor, host, database name, and username. The password prints as *******:

output
	kc.db-username =  keycloak (ENV)
	kc.db-url-database =  keycloak (ENV)
	kc.db =  postgres (ENV)
	kc.db-url-host =  postgres (ENV)
	kc.db-password =  ******* (ENV)

Inside the Keycloak container, localhost refers to the Keycloak container itself, not another PostgreSQL container. Use the PostgreSQL container name or Kubernetes Service DNS in KC_DB_URL_HOST when both services run in containers:

Keycloak container ──► postgres:5432
                       ↑
               PostgreSQL container name

Database creation, networking, PostgreSQL users, and a complete working deployment are covered in Configure Keycloak with PostgreSQL.

Full KC_DB_URL vs separate URL components

There are two ways to give Keycloak the database address.

Complete URL:

text
KC_DB_URL=jdbc:postgresql://postgres:5432/keycloak

Or the same pieces separately:

text
KC_DB_URL_HOST=postgres
KC_DB_URL_PORT=5432
KC_DB_URL_DATABASE=keycloak

Choose one approach. If the complete KC_DB_URL is set, Keycloak ignores the separate URL pieces. show-config can still list both if you set both; that listing does not mean they combine.

Hostname

Users may connect directly to Keycloak, or through a reverse proxy. For environment-variable syntax, start with the public URL:

bash
podman run --rm -e KC_HOSTNAME=https://auth.example.com quay.io/keycloak/keycloak:26.7.3 show-config

Keycloak stores that value as kc.hostname from the environment:

output
	kc.hostname =  https://auth.example.com (ENV)

KC_HOSTNAME can be a hostname or a full URL. Proxy headers, HTTP behind TLS termination, and certificate files depend on how Keycloak is exposed. Those layouts are covered in Keycloak HTTPS/TLS.

Health and metrics

Health endpoints let monitoring systems check whether Keycloak is running and ready. Metrics expose operational measurements that monitoring tools such as Prometheus can collect.

Confirm both resolve from the environment:

bash
podman run --rm \
  -e KC_HEALTH_ENABLED=true \
  -e KC_METRICS_ENABLED=true \
  quay.io/keycloak/keycloak:26.7.3 show-config
output
	kc.health-enabled =  true (ENV)
	kc.metrics-enabled =  true (ENV)

Both flags resolved from the environment. By default, /health and /metrics are exposed through Keycloak's management interface on port 9000. Enabling the variables does not publish that port on the host. In a container, you must also publish or map the management port if you want to open http://localhost:9000/health from the host.


Keycloak environment variables not working

When a KC_* value does not match what you expect, run show-config with the same -e / --env-file arguments you use for start. The source column (ENV, CLI, keycloak.conf) tells you which layer won.

The KC_ prefix is a mapping system, not a free-form namespace. A logical-looking name is not enough. The underlying option must actually exist.

Variable never entered the container

On a container host, export KC_HOSTNAME=… does not enter the container. Add -e, --env-file, or a Compose environment: entry.

For a running container, podman exec <container> env | grep KC_ shows whether the process has the name you set. If the container exits during startup, use podman inspect <container> to read the configured environment instead.

On a Linux ZIP install, an export in one shell or account is not inherited by another. If you start Keycloak with sudo, systemd, a second terminal, or a different user, set the variables in that process environment instead.

Wrong KC_* name

Confirm the name is a real Keycloak option. A typo such as KC_DB_HOST instead of KC_DB_URL_HOST never maps to db-url-host. Compare the hyphenated option against All configuration and read the Env column.

Older tutorials still show KEYCLOAK_ADMIN / KEYCLOAK_ADMIN_PASSWORD or KC_PROXY=edge. Current Keycloak 26 uses KC_BOOTSTRAP_ADMIN_* for the first admin and KC_PROXY_HEADERS (with hostname options) instead of the removed proxy setting.

KC_* does not make an unsupported option valid

The conversion rule works only for options Keycloak actually defines.

Existing option:

text
http-relative-path

Environment variable:

text
KC_HTTP_RELATIVE_PATH

KC_HOSTNAME_PATH looks related to URLs, but it is not a documented Keycloak option. show-config output alone does not prove that a guessed KC_* name corresponds to a supported Keycloak server option. Verify the original option in All configuration.

CLI option overrides the environment variable

Command-line flags beat environment variables for the same option. If show-config prints (CLI) for a setting you also passed with -e, remove the flag or align the values.

Bootstrap admin variables do not reset an existing administrator

KC_BOOTSTRAP_ADMIN_USERNAME and KC_BOOTSTRAP_ADMIN_PASSWORD create the first administrator only during initialization of Keycloak's built-in administrative realm (master). If master already exists, those values do not recreate or reset an administrator. Use the recovery procedure in Reset Keycloak admin password after the first start.

Build-time option appears ignored

KC_DB, KC_HEALTH_ENABLED, and KC_METRICS_ENABLED are classified as build-time options. With a normal start, Keycloak can apply them through an implicit build, so setting them on podman run often appears to work.

With a pre-built optimized image and start --optimized, those values must already match what was used at build time. Changing them only on podman run does not rebuild the server.

Password containing $ changes

Standard KC_* values run through expression evaluation. A password that contains $ or ${…} can change silently. On Keycloak 26.6 or later, switch that secret to KCRAW_DB_PASSWORD and do not set KC_DB_PASSWORD at the same time.


Build-time vs runtime environment variables

Some Keycloak settings change the server installation itself. Keycloak needs those values while preparing or building the server. Other settings, such as the database password or hostname, can be supplied whenever the already-built server starts.

BUILD TIME
Create/prepare Keycloak installation
        │
        ├── KC_DB
        ├── KC_HEALTH_ENABLED
        └── KC_METRICS_ENABLED
        │
        ▼
Built server
        │
        │ runtime settings
        ├── KC_DB_URL
        ├── KC_DB_USERNAME
        ├── KC_DB_PASSWORD
        └── KC_HOSTNAME
        ▼
Running Keycloak
Example Type Typical stage
KC_DB Build-time image / build
KC_HEALTH_ENABLED Build-time image / build
KC_METRICS_ENABLED Build-time image / build
KC_DB_URL_HOST Runtime container start
KC_DB_USERNAME Runtime container start
KC_DB_PASSWORD Runtime container start
KC_HOSTNAME Runtime container start

What build-time actually means

Build-time options change how the Keycloak server installation itself is prepared, for example which database driver is included. Runtime options supply values that can vary between deployments, such as the address and credentials of the PostgreSQL server.

From a Keycloak install directory, an explicit build looks like this:

bash
KC_DB=postgres bin/kc.sh build

A successful build reports that the configuration was updated and persisted:

output
Server configuration updated and persisted. Run the following command to review the configuration:

	kc.sh show-config

show-config then reports the vendor as persisted, for example kc.db = postgres (Persisted).

Runtime variables such as KC_DB_PASSWORD and KC_DB_URL_HOST belong on start or start-dev, not in the build layer. Build metadata is stored in plain text inside the image; never bake secrets into build.

Why KC_DB=postgres often appears in docker run

KC_DB is classified by Keycloak as a build-time option. With a normal non-optimized start, Keycloak can perform the necessary build automatically, which is why you often see KC_DB=postgres supplied when starting a container.

That is also why the PostgreSQL show-config example earlier could pass -e KC_DB=postgres without an explicit kc.sh build.

What changes with start --optimized

With a pre-built optimized image and start --optimized, the database vendor must already match what was used at build time. start --optimized skips the implicit build. If you change a build-time value later and start with --optimized, Keycloak warns that the new value will not apply until you rebuild.

Multi-stage Containerfiles and start --optimized are covered in Build an optimized Keycloak container.


Advanced: Preserve $ characters with KCRAW_*

Most values should use normal KC_* variables. One special case occurs when a password or other secret contains dollar signs ($).

A password such as pa$$word${abc} is the problem. Keycloak normally performs expression processing on environment-variable values, so the value it receives may not remain exactly as written:

KC_DB_PASSWORD=pa$$word${abc}
        │
        ▼  expression processing may alter $ and ${…}

KCRAW_DB_PASSWORD=pa$$word${abc}
        │
        ▼  keep the characters literally

KCRAW_* is available in Keycloak 26.6 and later. Use that prefix for any environment variable whose value must be preserved exactly as written.

When KC_DB_PASSWORD is enough

For passwords without $ characters, KC_DB_PASSWORD is enough. Hostnames, ports, and feature flags also stay on the KC_ prefix.

When to use KCRAW_DB_PASSWORD

Use KCRAW_DB_PASSWORD when the secret contains $ or ${ sequences your orchestrator injects verbatim.

Prefix Expression handling Typical use
KC_ ${var} expanded; $$ collapsed Hostnames, ports, feature flags
KCRAW_ $ characters escaped automatically Database passwords, API keys with $

You cannot set both KC_DB_PASSWORD and KCRAW_DB_PASSWORD for the same key. Keycloak fails at startup with a configuration error when both are present.


Advanced: Map unusual option names with KCKEY_*

Most users never need KCKEY_*. Skip this section unless you are configuring a key whose name contains characters that cannot be mapped cleanly to the normal KC_* form, most commonly logging categories.

Normally Keycloak can reconstruct the original option name from the environment variable. Converting _ back to - or . can become ambiguous for those keys. Logging categories often use dots in Java package names, so an underscore in the category can map to a dot instead of a dash. KC_LOG_LEVEL_PACKAGE_CLASS_NAME may therefore target log-level-package.class.name rather than log-level-package.class_name.

KCKEY_* lets you tell Keycloak which configuration key an environment variable represents. Pair a value variable with a KCKEY_* companion that names the real option:

Variable Role
KC_MYKEY=debug Holds the value
KCKEY_MYKEY=log-level-package.class_name Names the exact Keycloak key

Confirm the mapping with show-config:

bash
podman run --rm \
  -e KC_MYKEY=debug \
  -e KCKEY_MYKEY=log-level-package.class_name \
  quay.io/keycloak/keycloak:26.7.3 show-config
output
	kc.log-level-package.class_name =  debug (ENV)

You only need this pattern for awkward keys. Normal options such as KC_DB_URL_HOST do not require KCKEY_*.


Summary

Keycloak environment variables are NAME=value settings whose names normally begin with KC_. The same server option can be a CLI flag, a keycloak.conf line, or an environment variable. For ordinary options, uppercase the hyphenated name, replace hyphens with underscores, and add KC_. Confirm unknown names in All configuration instead of inventing a plausible KC_* string.

Pass variables with podman run -e or docker run -e for containers, --env-file for a list of names, a Compose environment: block, or export before kc.sh on a Linux ZIP install. Host export does not enter a container. show-config prints the resolved value and its source (ENV, CLI, Persisted) without starting the server or testing PostgreSQL.

Environment variables outrank keycloak.conf but lose to CLI flags. For PostgreSQL, pass KC_DB=postgres plus either a full KC_DB_URL or host-style KC_DB_URL_* components, not both. On a normal start, Keycloak can apply build-time options such as KC_DB through an implicit build. With start --optimized, those values should already be baked in. Use KCRAW_* when secrets contain $ characters, and KCKEY_* only when underscore mapping would hit the wrong key.


References


Frequently Asked Questions

Does Keycloak still read KEYCLOAK_ADMIN?

Older releases commonly used KEYCLOAK_ADMIN and KEYCLOAK_ADMIN_PASSWORD. In Keycloak 26, use KC_BOOTSTRAP_ADMIN_USERNAME and KC_BOOTSTRAP_ADMIN_PASSWORD for the first administrator. Those values bootstrap the initial administrator; they do not reset an existing administrator on later startups.

Can I mix KC_* variables and keycloak.conf?

Yes, for different options. For the same option, precedence picks one winner: environment beats the file unless a CLI flag overrides both.

Is KC_DB a runtime or build-time variable?

Database vendor (KC_DB=postgres) is a build-time option. A normal start without --optimized can apply it through an implicit build, which is why container examples often set KC_DB=postgres at start. On an optimized image started with --optimized, set KC_DB during kc.sh build. JDBC host, database name, username, and password are runtime options on start.

Can I set KC_DB_URL and KC_DB_URL_HOST together?

Do not combine them expecting the host, port, or database variables to edit the JDBC URL. When KC_DB_URL is set, Keycloak ignores the URL-component options.

Why does show-config mask my password?

Sensitive values such as KC_DB_PASSWORD print as asterisks by design. The line still confirms the variable is set and came from ENV.

When do I need KCRAW_* instead of KC_*?

Use KCRAW_ on Keycloak 26.6 and later when the value must keep dollar signs exactly as written, typically a password injected by a secrets manager. Do not set both prefixes for the same key.

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)