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 required means a client with a public IP address reached Keycloak over plain HTTP. Put a TLS proxy in front. Don't switch the realm to Require SSL: None outside a lab.
  • Behind a proxy that terminates TLS, set KC_PROXY_HEADERS=xforwarded and make the proxy send X-Forwarded-Proto, X-Forwarded-Host and X-Forwarded-Port. Without both, Keycloak builds http:// URLs, and you get a blank admin console and Invalid parameter: redirect_uri.
  • Set KC_HOSTNAME to the full public URL, such as https://auth.example.test. It fixes the token issuer, so apps that talk to http://keycloak:8080 stop failing with Invalid 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.

Keycloak error page in Chrome with the Keycloak logo and a white box that says We are sorry... HTTPS required, shown when the admin console is opened over plain HTTP from a public IP address

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://.

Diagram: the browser connects over HTTPS port 443 to nginx, which ends TLS and adds the Host, X-Forwarded-Proto, X-Forwarded-Host, X-Forwarded-Port and X-Forwarded-For headers, then forwards plain HTTP to Keycloak on port 8080 with KC_PROXY_HEADERS and KC_HOSTNAME set. An app container uses the http://keycloak:8080 backchannel, and port 9000 serves health checks without being proxied

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.

Terminal capture: without KC_PROXY_HEADERS, Keycloak behind nginx redirects to http://auth.example.test, reports an http issuer, rejects the admin console redirect_uri with HTTP 400 and Chrome blocks Mixed Content. After adding the X-Forwarded headers and KC_PROXY_HEADERS=xforwarded, the redirect, issuer and login form all use https

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.

Terminal capture: a token issued through https://auth.example.test is rejected at http://keycloak:8080 with 401 and the log reason Invalid token issuer. Expected http://keycloak:8080/realms/demo. With KC_HOSTNAME and KC_HOSTNAME_BACKCHANNEL_DYNAMIC set, the internal discovery document keeps the https issuer, uses internal token and certs endpoints, userinfo returns 200 and a forged X-Forwarded-Host no longer changes the issuer

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.

Keycloak 26.8.1 admin console loaded through nginx at https://auth.example.test, showing the master realm settings with Require SSL set to External requests and a banner about the temporary admin user

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.