Blazespectre
Blazespectre2mo ago

ZITADEL v4.1.4 — Init successful, but no console/login UI visible (Caddy reverse proxy)

I’m trying to get ZITADEL v4.1.4 running self-hosted with Docker Compose and Caddy as reverse proxy (--tlsMode external, TLS termination). Database: PostgreSQL 17. Containers start, logs look fine, DB initializes, and health checks return 200 — but I still can’t reach the UI. Both /ui/console and /ui/v2/login return a JSON 404 (“Not Found”), even when testing locally on port 8080. I’ve tried: • Correct ExternalDomain, ExternalSecure=true, tlsMode=external • Explicit ZITADEL_EXTERNALPORT=443 • Removing manual Login V2 envs • Proxying via h2c:8080 and also split-routing /ui/v2/login → :3000 • Fresh init with clean DB → Same 404 result every time. Questions: 1️⃣ In v4.1.x, is the separate zitadel-login container still needed, or should Login v2 be integrated in the main image? 2️⃣ What’s the minimal working config (env + Caddy) for /ui/console and Login V2 behind external TLS? 3️⃣ Which envs are required for this mode (e.g. ZITADEL_EXTERNALPORT=443)? 4️⃣ Should everything route via port 8080/h2c, or is 3000 still expected? 5️⃣ Is there an updated Compose + Caddy example for v4.1.x? Haven’t been able to get the UI working yet — backend and health are fine, but the console/login endpoints never render. Any help or pointers would be greatly appreciated 🙏 — Flo
1 Reply
Ask Inkeep
Ask Inkeep2mo ago
Hi @Blazespectre, I'm an AI assistant for ZITADEL. While a team member reviews this question, I'll see if I can find relevant sources for your question. Answer Based on the documentation, I can help clarify the configuration for ZITADEL with Caddy as a reverse proxy in external TLS mode. Separate Login Container Still Required Yes, the separate zitadel-login container is still needed (1) . The documentation shows both containers in the Docker Compose setup:
login-external-tls:
restart: 'unless-stopped'
image: 'ghcr.io/zitadel/zitadel-login:latest'
environment:
- ZITADEL_API_URL=<http://zitadel-external-tls:8080>
- NEXT_PUBLIC_BASE_PATH=/ui/v2/login
- ZITADEL_SERVICE_USER_TOKEN_FILE=/current-dir/login-client.pat
- CUSTOM_REQUEST_HEADERS=Host:127.0.0.1.sslip.io
volumes:
- '.:/current-dir:ro'
networks:
- app
depends_on:
zitadel-external-tls:
condition: 'service_healthy'
login-external-tls:
restart: 'unless-stopped'
image: 'ghcr.io/zitadel/zitadel-login:latest'
environment:
- ZITADEL_API_URL=<http://zitadel-external-tls:8080>
- NEXT_PUBLIC_BASE_PATH=/ui/v2/login
- ZITADEL_SERVICE_USER_TOKEN_FILE=/current-dir/login-client.pat
- CUSTOM_REQUEST_HEADERS=Host:127.0.0.1.sslip.io
volumes:
- '.:/current-dir:ro'
networks:
- app
depends_on:
zitadel-external-tls:
condition: 'service_healthy'
Required Environment Variables for External TLS Mode For external TLS mode, these environment variables are required :
environment:
ZITADEL_EXTERNALPORT: 443
ZITADEL_EXTERNALSECURE: true
ZITADEL_TLS_ENABLED: false
environment:
ZITADEL_EXTERNALPORT: 443
ZITADEL_EXTERNALSECURE: true
ZITADEL_TLS_ENABLED: false
Caddy Configuration for External TLS The Caddy configuration should route login requests to port 3000 and everything else to port 8080 via h2c :
<https://127.0.0.1.sslip.io> {
tls /etc/certs/selfsigned.crt /etc/certs/selfsigned.key
reverse_proxy /ui/v2/login/* <http://login-external-tls:3000>
reverse_proxy h2c://zitadel-external-tls:8080
}
<https://127.0.0.1.sslip.io> {
tls /etc/certs/selfsigned.crt /etc/certs/selfsigned.key
reverse_proxy /ui/v2/login/* <http://login-external-tls:3000>
reverse_proxy h2c://zitadel-external-tls:8080
}
Complete Setup Commands The documentation provides these setup commands for external TLS mode :
# Download the configuration files.
export ZITADEL_CONFIG_FILES=<https://raw.githubusercontent.com/zitadel/zitadel/main/docs/docs/self-hosting/manage/reverseproxy>
wget ${ZITADEL_CONFIG_FILES}/docker-compose.yaml -O docker-compose-base.yaml --quiet
wget ${ZITADEL_CONFIG_FILES}/caddy/docker-compose.yaml -O docker-compose-caddy.yaml --quiet
wget ${ZITADEL_CONFIG_FILES}/caddy/external-tls.Caddyfile -O external-tls.Caddyfile --quiet

# Generate a self signed certificate and key.
openssl req -x509 -batch -subj "/CN=127.0.0.1.sslip.io/O=Zitadel Demo" -nodes -newkey rsa:2048 -keyout ./selfsigned.key -out ./selfsigned.crt 2>/dev/null

# Run the database, and Caddy.
docker compose --file docker-compose-base.yaml --file docker-compose-caddy.yaml up --detach --wait db zitadel-init zitadel-external-tls login-external-tls proxy-external-tls
# Download the configuration files.
export ZITADEL_CONFIG_FILES=<https://raw.githubusercontent.com/zitadel/zitadel/main/docs/docs/self-hosting/manage/reverseproxy>
wget ${ZITADEL_CONFIG_FILES}/docker-compose.yaml -O docker-compose-base.yaml --quiet
wget ${ZITADEL_CONFIG_FILES}/caddy/docker-compose.yaml -O docker-compose-caddy.yaml --quiet
wget ${ZITADEL_CONFIG_FILES}/caddy/external-tls.Caddyfile -O external-tls.Caddyfile --quiet

# Generate a self signed certificate and key.
openssl req -x509 -batch -subj "/CN=127.0.0.1.sslip.io/O=Zitadel Demo" -nodes -newkey rsa:2048 -keyout ./selfsigned.key -out ./selfsigned.crt 2>/dev/null

# Run the database, and Caddy.
docker compose --file docker-compose-base.yaml --file docker-compose-caddy.yaml up --detach --wait db zitadel-init zitadel-external-tls login-external-tls proxy-external-tls
The key points are: - Both ZITADEL main container (port 8080) and login container (port 3000) are needed - External TLS mode requires ZITADEL_TLS_ENABLED: false with ZITADEL_EXTERNALSECURE: true - Caddy should proxy /ui/v2/login/* to the login container on port 3000, everything else to h2c://zitadel:8080 ​ To learn more, see the sources I considered: [1] Configure Zitadel with Caddy ​ Feel free to tag @Ask Inkeep with additional questions.

Did you find this page helpful?