Solving Keycloak Internal vs External Access in Kubernetes with hostname-backchannel-dynamic

Published: (February 17, 2026 at 08:23 AM EST)
3 min read
Source: Dev.to

Source: Dev.to

Introduction

Using OpenID Connect (OIDC) as an authentication source is a best practice when working with infrastructure, as it significantly improves both security and maintainability. Keycloak is an excellent open‑source project widely adopted for this purpose. It supports many features and storage backends (such as PostgreSQL) and has straightforward deployment instructions on its official website.

However, deploying Keycloak in Kubernetes can expose an internal service communication issue that requires a specific configuration.

The Problem: External Hostname vs Internal Access

When deploying Keycloak in Kubernetes, you typically specify a public hostname with the --hostname=https://auth.example.com parameter. This works perfectly for external clients accessing your authentication service.

The challenge appears when other services running inside the same Kubernetes cluster—e.g., a container registry or CI server—need to authenticate with Keycloak. These services must reach the discovery URL:

https://auth.example.com/realms/{realm-name}/.well-known/openid-configuration

Keycloak always redirects to (and generates tokens/URLs based on) the hostname supplied at startup. If that public URL is not resolvable from within the cluster, internal pods cannot reach Keycloak for back‑channel requests (token introspection, userinfo, etc.), even though they can reach the pod via internal DNS.

The Solution: Dynamic Backchannel Hostname

Keycloak provides a CLI option to solve this issue (available when the hostname:v2 feature is enabled):

--features=hostname:v2
--hostname-backchannel-dynamic=true

With this configuration, Keycloak dynamically determines the back‑channel (internal) URLs based on the incoming request, allowing access via:

  • Direct IP addresses
  • Internal Kubernetes DNS (e.g., keycloak.keycloak-namespace.svc.cluster.local:8080/realms/{realm-name}/.well-known/openid-configuration)

How It Works

When --hostname-backchannel-dynamic=true is enabled:

Access TypeURL Used
Externalhttps://auth.example.com (public hostname)
InternalInternal Kubernetes service DNS name (or pod IP)

This dual‑access approach ensures that:

  • External clients receive the proper public URL for authentication flows.
  • Internal services can reliably reach Keycloak using cluster‑internal DNS resolution.
  • No additional network routing or ingress configuration is required solely for internal communication.

Note for production: Ensure your ingress/reverse proxy forwards Forwarded or X‑Forwarded‑* headers correctly, and consider enabling HTTPS on both external and internal paths.

Example Configuration

Below is a minimal Kubernetes deployment manifest that enables the dynamic backchannel hostname:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: keycloak
spec:
  template:
    spec:
      containers:
        - name: keycloak
          image: quay.io/keycloak/keycloak:latest
          args:
            - start
            - --features=hostname:v2           # required for dynamic backchannel
            - --hostname=https://auth.example.com
            - --hostname-backchannel-dynamic=true
            - --db=postgres
            - --proxy-headers=forwarded        # important for correct header handling behind proxy/ingress
            # ... other configuration (ports, HTTPS, DB credentials via env vars, etc.)

Conclusion

The --hostname-backchannel-dynamic=true flag (combined with the hostname:v2 feature) provides a simple yet powerful solution for mixed internal/external access scenarios in Kubernetes. The public URL remains ideal for external client access, while internal service‑to‑service communication benefits from the flexibility of dynamic backchannel resolution.

Keycloak’s hostname configuration options make it a robust choice for authentication infrastructure in containerized environments.

References

0 views
Back to Blog

Related posts

Read more »