Keycloak Behind a Reverse Proxy: Fix HTTPS Required

You open Keycloak in the browser and get a grey page that says We are sorry... HTTPS required. Or you put it behind nginx, and the admin console stays blank, or the login page says Invalid parameter: redirect_uri. Your app then rejects every token with Invalid token issuer. All of these come from the same question: what URL does Keycloak think it lives at?
This guide reproduces each error with quay.io/keycloak/keycloak:26.8.1, nginx:1.31.8 and postgres:18.6 in Docker Compose and fixes it. Every error message below comes from that lab run on November 4, 2026. The lab used the name auth.example.test with a self-signed certificate in place of your real domain.
These errors are common. On Stack Overflow, keycloak Invalid parameter: redirect_uri had 505,136 views on November 4, 2026, and "HTTPS required" while logging in to Keycloak as admin had 136,879.
The short version:
-
HTTPS requiredmeans a client with a public IP address reached Keycloak over plain HTTP. Put a TLS proxy in front. Don't switch the realm toRequire SSL: Noneoutside a lab. - Behind a proxy that terminates TLS, set
KC_PROXY_HEADERS=xforwardedand make the proxy sendX-Forwarded-Proto,X-Forwarded-HostandX-Forwarded-Port. Without both, Keycloak buildshttp://URLs, and you get a blank admin console andInvalid parameter: redirect_uri. - Set
KC_HOSTNAMEto the full public URL, such ashttps://auth.example.test. It fixes the token issuer, so apps that talk tohttp://keycloak:8080stop failing withInvalid token issuer. - For your own apps, the redirect URI must match the client's Valid redirect URIs exactly, including
https, the host and the port.
Fix Keycloak startup errors in production mode
Most Compose examples on the internet use start-dev. When you switch to start, the production mode, the container exits before it serves a single page.
Key material not provided to setup HTTPS
With command: start and only the admin variables set, the container stopped with exit code 2 and this line:
Key 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. Production mode expects its own TLS certificate. When a reverse proxy handles TLS, as in this guide, set KC_HTTP_ENABLED: "true" so Keycloak listens on plain HTTP port 8080 inside the Docker network. The reverse proxy guide requires it for this setup: "If the TLS connection is terminated at the reverse proxy (edge termination), enabling HTTP through the http-enabled setting is required."
hostname is not configured
With HTTP enabled, the next start failed with exit code 1:
ERROR: hostname is not configured; either configure hostname, or set hostname-strict to false The quick fix you see in many answers is KC_HOSTNAME_STRICT: "false". It works, but it tells Keycloak to build every URL from the request, which causes the next two problems. Set KC_HOSTNAME instead, as shown in the last section. Keycloak also logged a warning that "the server is running in an insecure context" while HTTPS was off, proxy-headers was unset and hostname-strict was false.
Fix "HTTPS required" in Keycloak
This is the error on the grey Keycloak page. In the lab it came from the master realm login page, where the admin console sends you to sign in.
Why Keycloak asks for HTTPS
Every realm has a Require SSL setting. Its default, external, allows plain HTTP only from private addresses. The Keycloak server admin guide describes it: "If you try to access Keycloak without SSL from a non-private IP address, you will get an error."
That explains why it works on your laptop and fails on a VPS. In the lab, Keycloak first ran on the default Compose network, 192.168.0.0/20, and the login page loaded over http://127.0.0.1:8080. Then Keycloak moved to a Docker network in 203.0.113.0/24, a documentation range that counts as public, so it saw requests from 203.0.113.1. The same URL returned HTTP 400 with HTTPS required, and the log named the reason:
type="LOGIN_ERROR", realmName="master", clientId="null", userId="null", ipAddress="203.0.113.1", error="ssl_required" A VPS reached from the internet is in the same position. Your browser's address is public, so plain HTTP to port 8080 fails.
Serve Keycloak over HTTPS, not Require SSL: None
The fix is HTTPS in front of Keycloak, which the rest of this guide sets up with nginx. If you already use Nginx Proxy Manager or Traefik, use that proxy and copy the header settings below.
Many answers suggest switching the master realm to Require SSL: None. It does work. From inside the container, where localhost is a private address, kcadm.sh changed the setting:
docker compose exec keycloak /opt/keycloak/bin/kcadm.sh config credentials \ --server http://localhost:8080 --realm master --user admin --password '<admin password>' docker compose exec keycloak /opt/keycloak/bin/kcadm.sh update realms/master -s sslRequired=NONE The login page loaded over plain HTTP from the public range right after. But the admin password and tokens then cross the internet unencrypted. The same guide says None "applies only in development". The lab set it back with -s sslRequired=EXTERNAL, and HTTPS required returned.
Fix a blank admin console and Invalid parameter: redirect_uri behind nginx
Once nginx terminates TLS and forwards plain HTTP to Keycloak, a new set of errors appears. Keycloak no longer sees HTTPS, so it generates http:// links for a site that you reach over https://.
What happens without the forwarded headers
The first nginx config in the lab only passed proxy_set_header Host $host, and Keycloak had KC_HOSTNAME_STRICT=false with no proxy settings. Opening https://auth.example.test/admin/ returned:
Location: http://auth.example.test/admin/master/console/ The OpenID discovery document listed http://auth.example.test/realms/master as the issuer. In Chrome, the admin console stayed blank with a spinner, and the browser console showed why:
Mixed Content: The page at 'https://auth.example.test/admin/master/console/' was loaded over HTTPS, but requested an insecure resource 'http://auth.example.test/resources/master/admin/en'. This request has been blocked; the content must be served over HTTPS. The admin console's own sign-in failed too. A request with redirect_uri=https://auth.example.test/admin/master/console/ got HTTP 400 and Invalid parameter: redirect_uri, because Keycloak expected the http:// version.
Adding the headers to nginx alone changed nothing. With X-Forwarded-Proto and the others set, the redirect still went to http://, because Keycloak ignores those headers until you enable them. The reverse also failed: with KC_PROXY_HEADERS=xforwarded but the old nginx config, the redirect still pointed to http://. You need both.
Forward the headers and set KC_PROXY_HEADERS
This nginx server block fixed it, together with KC_PROXY_HEADERS: xforwarded on the Keycloak service:
server { listen 80; server_name auth.example.test; return 301 https://$host$request_uri; } server { listen 443 ssl; server_name auth.example.test; ssl_certificate /etc/nginx/certs/auth.crt; ssl_certificate_key /etc/nginx/certs/auth.key; location / { proxy_pass http://keycloak:8080; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Port $server_port; } } After the change, /admin/ redirected to https://auth.example.test/admin/master/console/, the issuer started with https://, and the admin console loaded with 0 console errors. $server_port is the port nginx listens on inside its container. If you publish nginx on another host port, such as 8443, put that public port in X-Forwarded-Port instead.
Never publish Keycloak's port 8080 next to the proxy once headers are trusted. In the lab, a container on the same network sent X-Forwarded-Host: evil.example straight to keycloak:8080, and the discovery document answered with the issuer https://evil.example/realms/master. The reverse proxy guide warns that clients could "send forged forwarded headers directly to Keycloak" without network restrictions. The next section closes this hole for the host name.
Invalid parameter: redirect_uri in your own apps
The same error appears for your own clients when the redirect URI an app sends doesn't match the client's Valid redirect URIs. The lab client app allowed https://app.example.test/*:
| redirect_uri sent | Result |
|---|---|
https://app.example.test/callback | login form |
https://app.example.test | login form |
http://app.example.test/callback | Invalid parameter: redirect_uri |
https://app.example.test:8443/callback | Invalid parameter: redirect_uri |
https://www.app.example.test/callback | Invalid parameter: redirect_uri |
The scheme, the host and the port all have to match. The Keycloak log shows the URI that was rejected, which is the fastest way to see what your app sent:
type="LOGIN_ERROR", realmName="demo", clientId="app", ... error="invalid_redirect_uri", redirect_uri="https://www.app.example.test/callback" An http:// URI usually means your app sits behind its own proxy and builds its callback URL from the plain HTTP request. Fix the app's proxy settings or set its public URL, the same way you fixed Keycloak. The Home Assistant and Nextcloud guides show this for two common apps.
Fix "Invalid token issuer" with KC_HOSTNAME
Even with the proxy headers fixed, KC_HOSTNAME_STRICT=false makes Keycloak build every URL from the request, including the iss claim in tokens. Your browser and your app's backend reach it at different addresses, so they disagree.
Why the issuer changes
In the lab, a user logged in through https://auth.example.test, so the access token had iss: https://auth.example.test/realms/demo. An app container then checked that token at http://keycloak:8080, the address it uses inside the Docker network. The userinfo endpoint answered 401 Unauthorized with error="invalid_token", error_description="Token verification failed", and the Keycloak log gave the real reason:
reason="Invalid token issuer. Expected 'http://keycloak:8080/realms/demo'" The same token worked through the public URL. Keycloak expected the issuer of the address that the request came in on.
Set the full public URL
Set KC_HOSTNAME: https://auth.example.test, the full URL with the scheme. With that in place, the discovery document at http://keycloak:8080 listed https://auth.example.test/realms/demo as the issuer, and the internal userinfo call returned HTTP 200. The forged X-Forwarded-Host: evil.example request from the previous section now got the correct issuer back. The hostname guide gives this as the reason the option exists: "By explicitly setting the hostname option, we avoid a situation where tokens could be issued by a fraudulent issuer."
One catch remains. With only KC_HOSTNAME set, the internal discovery document pointed the token and certs endpoints at https://auth.example.test too, so a backend inside Docker has to reach the public name. Add KC_HOSTNAME_BACKCHANNEL_DYNAMIC: "true". In the lab, the internal document then listed http://keycloak:8080/realms/demo/protocol/openid-connect/token and .../certs, kept the https:// issuer and the https:// login URL, and a token requested internally still carried iss: https://auth.example.test/realms/demo. Browsers that use the public URL saw only https:// endpoints.
A Keycloak Compose file with nginx that avoids these errors
This file puts the fixes together. It ran verbatim in the lab with the nginx config from above saved as nginx/keycloak.conf, and the certificate and key in certs/. Use a real certificate for your domain, for example from your existing proxy.
The compose file
Create .env with two random passwords:
printf 'KC_DB_PASSWORD=%s\nKC_ADMIN_PASSWORD=%s\n' "$(openssl rand -hex 24)" "$(openssl rand -hex 16)" > .env services: postgres: image: postgres:18.6 restart: unless-stopped environment: POSTGRES_USER: keycloak POSTGRES_DB: keycloak POSTGRES_PASSWORD: ${KC_DB_PASSWORD:?set KC_DB_PASSWORD in .env} volumes: - pgdata:/var/lib/postgresql healthcheck: test: ["CMD", "pg_isready", "-U", "keycloak", "-d", "keycloak"] interval: 5s timeout: 3s retries: 10 keycloak: image: quay.io/keycloak/keycloak:26.8.1 restart: unless-stopped command: start environment: KC_DB: postgres KC_DB_URL: jdbc:postgresql://postgres:5432/keycloak KC_DB_USERNAME: keycloak KC_DB_PASSWORD: ${KC_DB_PASSWORD:?set KC_DB_PASSWORD in .env} KC_HOSTNAME: https://auth.example.test KC_HOSTNAME_BACKCHANNEL_DYNAMIC: "true" KC_PROXY_HEADERS: xforwarded KC_HTTP_ENABLED: "true" KC_HEALTH_ENABLED: "true" KC_BOOTSTRAP_ADMIN_USERNAME: admin KC_BOOTSTRAP_ADMIN_PASSWORD: ${KC_ADMIN_PASSWORD:?set KC_ADMIN_PASSWORD in .env} healthcheck: test: ["CMD-SHELL", "exec 3<>/dev/tcp/127.0.0.1/9000 && printf 'GET /health/ready HTTP/1.1\\r\\nHost: localhost\\r\\nConnection: close\\r\\n\\r\\n' >&3 && grep -q UP <&3"] interval: 10s timeout: 5s retries: 12 start_period: 30s depends_on: postgres: condition: service_healthy nginx: image: nginx:1.31.8 restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./nginx/keycloak.conf:/etc/nginx/conf.d/default.conf:ro - ./certs:/etc/nginx/certs:ro depends_on: keycloak: condition: service_healthy volumes: pgdata: Keycloak has no published port, so only nginx can reach it. The Keycloak image has no curl, so the healthcheck talks to the management port 9000 with bash's /dev/tcp. Port 9000 stays off the proxy, as the reverse proxy guide recommends. If Postgres refuses the password on a rerun, the Postgres password guide explains why the variables only apply on the first start.
Check that it works
Run docker compose up -d. Compose waited for each service in turn:
Container kcfinal-postgres-1 Healthy Container kcfinal-keycloak-1 Starting Container kcfinal-keycloak-1 Started Container kcfinal-keycloak-1 Waiting Container kcfinal-keycloak-1 Healthy Container kcfinal-nginx-1 Starting Container kcfinal-nginx-1 Started Keycloak logged Keycloak 26.8.1 on JVM (powered by Quarkus 3.40.1) started in 2.962s, with no insecure context warning and no errors. http://auth.example.test/admin/ returned a 301 to https://, https://auth.example.test/admin/ redirected to the https:// console, and the admin login through Chrome worked.
The banner at the top is Keycloak's reminder that the bootstrap admin is temporary. Create a permanent admin account and delete the bootstrap one. For an internet-facing server, the reverse proxy guide also recommends that you don't expose /admin/ and /realms/master/ publicly, because "Exposed admin paths lead to an unnecessary attack vector." Your apps only need /realms/, /resources/ and /.well-known/.
FAQ
Errors on the login page
How do I fix "HTTPS required" in Keycloak?
Serve Keycloak over HTTPS, usually through a reverse proxy, and set KC_PROXY_HEADERS and KC_HOSTNAME so Keycloak knows the public URL is https://. The error appears when a client with a public IP address uses plain HTTP while the realm's Require SSL is external.
Should I set Require SSL to None in the master realm?
Only in a throwaway lab. It removes the error, but logins and tokens then travel unencrypted. Keycloak's server admin guide limits None to development. Fix HTTPS instead.
Why does the Keycloak admin console load forever behind a reverse proxy?
Keycloak builds http:// URLs because it doesn't know the proxy uses HTTPS, and the browser blocks them as Mixed Content. Send X-Forwarded-Proto, X-Forwarded-Host and X-Forwarded-Port from the proxy and set KC_PROXY_HEADERS=xforwarded. The browser console lists the blocked http:// request.
Configuration
What causes "Invalid parameter: redirect_uri" in Keycloak?
The redirect_uri sent by the app doesn't match any Valid redirect URI of the client. Check the scheme, host and port. The Keycloak log shows the rejected value in the invalid_redirect_uri event. For the admin console itself, it means Keycloak computed an http:// URL behind the proxy.
How do I fix "Invalid token issuer" when Keycloak runs in Docker?
Set KC_HOSTNAME to the full public URL so the issuer is the same no matter which address an app uses. Add KC_HOSTNAME_BACKCHANNEL_DYNAMIC=true if your backend should call Keycloak at http://keycloak:8080 inside the Docker network.
Can Keycloak run in production mode with HTTP only?
Yes, behind a proxy that handles HTTPS. Set KC_HTTP_ENABLED=true, KC_PROXY_HEADERS and KC_HOSTNAME, and don't publish port 8080. Without http-enabled, start exits with Key material not provided to setup HTTPS.
原文:https://www.a8q9.cn/en/article/keycloak-behind-a-reverse-proxy-fix-https-required-78bf57.html



