Recommended reverse proxy setup example and headers question

I am having a lot of difficulties setting up my reverse proxy and reaching the admin console. Can somebody help me or give me any reverse proxy setup that works on their server? Yes, I have looked through other posts here.

I can’t seem to find any example config that adheres to the official recommendations here Using a reverse proxy - Keycloak. I will write down what my tries for a setup that adheres to the official reverse proxy guide, and where my problems are.

This is the summary of what I have observed, and I should note that I use NixOS.

  • Everything works fine if I don’t set the headers (X-Forwarded-*, etc), but the official recommendations strongly advise to set these headers.
  • When setting the proxy_set_headers (I’ll get to that a bit lower), everything works if I just expose the root directory /, but obviously that is not very secure. If I put a basic auth infront of the vulnerable locations (which is not an ideal setup anyway), then I also cannot reach the admin console.
  • If I set the proxy_set_headers, and expose only /js/, /realms/, /resources/, and /robots.txt, and furthermore set hostname-admin in the keycloak config to localhost, I can’t seem to reach the admin console when I reach it via ssh port forwarding (I use ssh -L 8080:localhost:8080 mykeycloak.server). I get an “invalid parameter: redirect_uri” error. Removing (some of) the proxy_set_header in the nginx config gives me other problems, like an infinite “Loading the admin console” screen, and when I remove enough of those proxy_set_headers, then it works (as I mentioned in the first bullet point).

I am sorry to say that I am not too knowledgeable about how these headers work.

This is my Keycloak configuration:

db=postgres
db-password=somepassword
db-url-database=keycloak
db-url-host=localhost
db-url-port=5432
db-url-properties=
db-username=keycloak
hostname=mykeycloak.server
hostname-admin=localhost
hostname-strict-backchannel=false
http-host=0.0.0.0
http-port=8080
http-relative-path=/
https-certificate-file=/run/keycloak/ssl/ssl_cert
https-certificate-key-file=/run/keycloak/ssl/ssl_key
https-port=8443
proxy=edge

This is my nginx configuration (yeah sorry I run NixOS so it adds a lot of clutter):

pid /run/nginx/nginx.pid;
error_log stderr;
daemon off;
events {
}
http {
        # The mime type definitions included with nginx are very incomplete, so
        # we use a list of mime types from the mailcap package, which is also
        # used by most other Linux distributions by default.
        include /nix/store/<long-nix-store-hash>-mailcap-2.1.53/etc/nginx/mime.types;
        # When recommendedOptimisation is disabled nginx fails to start because the mailmap mime.types database
        # contains 1026 enries and the default is only 1024. Setting to a higher number to remove the need to
        # overwrite it because nginx does not allow duplicated settings.
        types_hash_max_size 4096;
        include /nix/store/<long-nix-store-hash>-nginx-1.22.1/conf/fastcgi.conf;
        include /nix/store/<long-nix-store-hash>-nginx-1.22.1/conf/uwsgi_params;
        default_type application/octet-stream;
        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
        proxy_redirect          off;
        proxy_connect_timeout   60s;
        proxy_send_timeout      60s;
        proxy_read_timeout      60s;
        proxy_http_version      1.1;
        include /nix/store/<long-nix-store-hash>-nginx-recommended-proxy-headers.conf;
        # $connection_upgrade is used for websocket proxying
        map $http_upgrade $connection_upgrade {
                default upgrade;
                ''      close;
        }
        client_max_body_size 10m;
        server_tokens off;
        server {
                listen 0.0.0.0:443 http2 ssl ;
                listen [::0]:443 http2 ssl ;
                listen 0.0.0.0:80 ;
                listen [::0]:80 ;
                server_name mykeycloak.server ;
                # Rule for legitimate ACME Challenge requests (like /.well-known/acme-challenge/xxxxxxxxx)
                # We use ^~ here, so that we don't check any regexes (which could
                # otherwise easily override this intended match accidentally).
                location ^~ /.well-known/acme-challenge/ {
                        root /var/lib/acme/acme-challenge;
                        auth_basic off;
                }
                root /var/www/mykeycloak.server;
                ssl_certificate /var/lib/acme/mykeycloak.server/fullchain.pem;
                ssl_certificate_key /var/lib/acme/mykeycloak.server/key.pem;
                ssl_trusted_certificate /var/lib/acme/mykeycloak.server/chain.pem;
                location /js/ {
                        proxy_pass http://localhost:8080/js/;
                        include /nix/store/<long-nix-store-hash>-nginx-recommended-proxy-headers.conf;
                }
                location /realms/ {
                        proxy_pass http://localhost:8080/realms/;
                        include /nix/store/<long-nix-store-hash>-nginx-recommended-proxy-headers.conf;
                }
                location /resources/ {
                        proxy_pass http://localhost:8080/resources/;
                        include /nix/store/<long-nix-store-hash>-nginx-recommended-proxy-headers.conf;
                }
                location /robots.txt {
                        proxy_pass http://localhost:8080/robots.txt;
                        include /nix/store/<long-nix-store-hash>-nginx-recommended-proxy-headers.conf;
                }
        }
}

Important: /nix/store/<long-nix-store-hash>-nginx-recommended-proxy-headers.conf contains the following:

proxy_set_header        Host $host;
proxy_set_header        X-Real-IP $remote_addr;
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-Server $host;

Removing the proxy_set_headers Host and X-Forwarded-Proto and X-Real-IP resolves my issues iirc (removing different ones gives me different issues when trying to reach the admin console), and removing all of it definitely resolves my issues, as mentioned before. But also as mentioned, setting X-Forwarded Proto was recommended by the official documentation. Also, it recommends setting X-Forwarded-Port. Furthermore, I have no idea how I would set Forward as per RFC7239.

Can somebody give me any reverse proxy config that works on their server? I am willing to change the setup, I don’t have any super specific requirements/restrictions.

Any help is appreciated.

Hey @berber

Here is mine, perhaps this will help.

cat > /etc/nginx/conf.d/keycloak.conf <<EOF
upstream keycloak {
    # Use IP Hash for session persistence
    ip_hash;
  
    # List of Keycloak servers
    server 192.168.1.101:8080;
}
  
      
server {
    listen 80;
    server_name lab-keycloak.keycloak.local;
 
    # Redirect all HTTP to HTTPS
    location / {   
      return 301 https://\$server_name\$request_uri;
    }
}
  
server {
    listen 443 ssl http2;
    server_name lab-keycloak.keycloak.local;
 
    ssl_certificate /etc/pki/tls/certs/my-cert.cer;
    ssl_certificate_key /etc/pki/tls/private/my-key.key;
    ssl_session_cache shared:SSL:1m;
    ssl_prefer_server_ciphers on;
 
    location / {
      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto $scheme;
      proxy_pass http://keycloak;
    }
}
EOF
1 Like

Thank you for your reply!

If I try your config, I would still have the same problems I’ve had before:

  • The root directory is exposed, which in particular exposes /admin/.
  • Not all of the proxy_set_headers are set that are mentioned in the official reverse proxy recommendations.

I’m using a similar reverse proxy setup in my project, but I’m struggling in connect keycloak to LDAP over SSL, can you give a check in my question

Hey @lajibolala

I posted a while back, not sure if this will help but over TCP/TLS using LDAPS

1 Like