Featured image thumbnail for post 'Self-hosting SSO with Nginx (Part 1): Keycloak'

Self-hosting SSO with Nginx (Part 1): Keycloak

How to use Docker and Nginx to get started with self-hosting single sign-on with Keycloak.

Joey Miller • Last updated July 06, 2023

This guide is the first part in a multi-part series of guides:

The dream

There are plenty of great services to self-host, including Nextcloud, and Tandoor Recipes. If you've ever tried self-hosting more than a few services you'll understand the frustration of remembering many different passwords and continuously having to log in. After doing some research, I realized my homelab needed "single sign-on" (SSO). SSO is an authentication method (typically viewed as an enterprise feature) that allows secure authentication with many services using "just one set of credentials". Using SSO I would be able to achieve my dream of needing only a single-login event to access all of my services.

Overview of setup

There are many tools that we can use for SSO, such as Authelia, Authentik, or Keycloak.

Although some of the aforementioned SSO tools may be easier to set up, I decided to go with Keycloak. Keycloak is an enterprise-level tool that is supported by Redhat. Using Keycloak will give us a lot of flexibility, and ticks the boxes for acceptable memory usage, theme-ability, and multi-factor authentication support.

Keycloak supports:

  • Single sign-on and sign out for browser applications
  • SAML, OAuth 2.0, OpenID Connect
  • LDAP (needs to be federated with an external LDAP service). For setting up authentication with services that need LDAP, see part 3 of this guide series.


This guide assumes you are using Docker + Nginx Proxy Manager (NPM) as the reverse proxy. This guide will be easy to adapt to bare Nginx.

Within Nginx Proxy Manager (NPM), I will be assuming you have set up SSL and are enforcing HTTPS for each proxy host. Otherwise, additional setup may be required - especially when dealing with OAuth2 Proxy in part 2 of this guide series.

Setting up Keycloak and Nginx Proxy Manager

To get started with Keycloak, add the following to the services: section of your docker-compose.yml file:

        image: 'jc21/nginx-proxy-manager:latest'
            - '80:80'
            - '81:81'
            - '443:443'
            - ./data/nginx_proxy_manager/data:/data
            - ./data/nginx_proxy_manager/letsencrypt:/etc/letsencrypt
        restart: unless-stopped

        # internal: keycloak on port 8080
        image: quay.io/keycloak/keycloak:latest
        command: start
            KC_HOSTNAME: 'auth.example.com'
            KC_PROXY: 'edge'
            KEYCLOAK_ADMIN: 'admin'
            KEYCLOAK_ADMIN_PASSWORD: 'admin'
            - ./data/keycloak:/opt/keycloak/data
        restart: unless-stopped

Note: The above example will have Keycloak running with the built-in H2 database. It is recommended in production to use an external database (i.e. MySQL, PostgreSQL, etc).

Then, let's configure Keycloak on a subdomain in the NPM web interface. We will host Keycloak at https://auth.example.com/auth and the OAuth proxy (explained later in this guide) at https://auth.example.com/oauth2. Replace example.com with your domain name.

Info: The default NPM username is [email protected], and the password is changeme. Make sure to change these.

  1. From the NPM dashboard, click Add Proxy Host

  2. In the Details tab:

    • Set Domain Names to auth.example.com
    • Set Scheme, Forward Hostname / IP, and Forward Port to http, keycloak, 8080 (respectively)
  3. Lets hide the default dashboard, and redirect the root path to the administrative console. In the Custom locations tab, add a location.

    • Set Define location to = /
    • Set Scheme, Forward Hostname / IP, and Forward Port to http, keycloak, 8080 (respectively)
    • Click the settings cog and set the field to return 301 /admin;
  4. In the Advanced tab:

    • Set the Custom Nginx Configuration field to:
proxy_buffer_size   128k;
proxy_buffers   4 256k;
proxy_busy_buffers_size   256k;

A screenshot showing the 'Custom Locations' tab for the Keycloak host in NPM.
The custom locations for the 'auth.example.com' proxy host.

Restricting users from services

We don't always want every user to be able to access every service. For example, you may want to give a friend access to your recipe application but not to the Pihole admin console.

If you have a compliant OIDC Client Application, setting Client authentication and Authorization to ON will be enough to access the Authorization tab for the client. Then you will be able to create a Group-based policy from the Policies sub-tab.

Unfortunately, this isn't always suitable - such as if the client uses SAML for authentication/authorization. In this case, another way to handle this in Keycloak (without needing any extensions) is to duplicate the default browser flow and add a condition that denies access if the user doesn't have the required role.

Note: This method requires a separate browser flow for each unique set of roles you would like to mandate for users of clients/services.

For more details see the answers by @Stuck and @heilerich on StackOverflow.

Enabling 2FA

To increase the security of your authentication process, Keycloak allows enabling two-factor authentication (2FA) for users. This requires users to provide a valid one-time-pass (OTP) from an authenticator app on their smartphone.

  1. Go to the Keycloak Administration Console

  2. Go to Authentication > Required actions and for Configure OTP toggle Set as default action to On.

    • This gives new users the ability to configure 2FA
  3. Go to the Users page. By clicking on each existing user, add Configure OTP to Required user actions from the User details > Details tab.

After these steps - on the next login users will be required to set up their authenticator for 2FA.

What next?

We've successfully set up a basic SSO implementation with Keycloak.

If all the services you are using are capable of authentication via OAuth, SAML, or Keycloak - the journey ends here for you. This implementation will be sufficient.

If you have other services that expect HTTP Header Auth or manage their own login flow (i.e. via LDAP) - continue reading. This guide continues in:

If you found this post helpful, please share it around: