DEV Community

Mustafa ERBAY
Mustafa ERBAY

Posted on • Originally published at mustafaerbay.com.tr

Single Sign-On (SSO) in Your Homelab

When the number of services on my homelab server reached double digits, managing separate passwords for each application and keeping track of which ones had two-factor authentication (2FA) enabled became a massive operational burden. Open tabs in the browser, user tables kept in separate databases for each service, and the risk of passwords traveling in the clear—even on a local network—forced me to set up a single, centralized authentication point. In this post, I will explain how to build a Single Sign-On (SSO) in your Homelab architecture from scratch using Authentik, maximizing security for both your local and publicly exposed services.

By the time you complete this guide, you will have a single authentication wall standing in front of all your services running on Docker. Once users log in, they will be able to access all internal services within their authorization scope (Portainer, Grafana, Home Assistant, etc.) without entering passwords, using just a single OTP (One-Time Password) or a hardware key.


Why Do You Need Single Sign-On (SSO) in Your Homelab?

Many services running on a local network either come without authentication by default or offer built-in login mechanisms that are vulnerable to brute-force attacks. When I realized that even a simple monitoring interface running on my own server could expose my entire network topology if leaked to the internet, I knew that putting a security layer in front of every service was a must. Setting up a Single Sign-On (SSO) in your Homelab doesn't just save you the hassle of memorizing passwords; it also standardizes logging and monitoring by routing all network traffic through a single gateway.

Traditionally, defining strong passwords for each application individually is not a sustainable method. When an application update is delayed or a zero-day vulnerability emerges, that service's login screen becomes a direct target for attackers. Thanks to the SSO architecture, we completely disable (or bypass) the application's own login mechanism and hand over the authentication load entirely to a tool dedicated to this job, which is constantly updated and adheres to security standards (OAuth2, OIDC, SAML).

Diagram


What is Authentik and Why Did I Choose It Over Other Alternatives?

There are popular open-source authentication solutions on the market like Authelia, Keycloak, and Authentik. While Keycloak is the enterprise standard, its resource consumption (due to being JVM-based) and its complex interface at a homelab scale make it a bit heavy for those of us who want maximum performance with minimal resources. Authelia, on the other hand, is an extremely lightweight and successful alternative, but the lack of a visual management interface and having to do all configuration via YAML files makes the process sluggish when dynamically adding new services or managing users.

Authentik brings together the best of both worlds. Its modern architecture written in Python and Go offers a rich web interface while allowing you to define highly flexible policies and flows in the background. Thanks to its Outpost architecture, it natively supports reverse proxy integration (Forward Auth) and can talk seamlessly with directory services like LDAP/Active Directory on your local network.


Authentik Installation: Spinning It Up with Docker Compose

The most stable way to run Authentik is by using Docker Compose. Before starting the installation, make sure Docker and the Docker Compose plugin are installed on your server. Authentik consists of several components: a web server, a background worker, Redis for caching, and a PostgreSQL database for data storage.

The docker-compose.yml file below defines a network structure optimized for a homelab environment, featuring local volume shares and isolation against external attacks.

version: '3.8'

services:
  postgresql:
    image: postgres:16-alpine
    container_name: authentik-postgres
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
      start_period: 20s
      interval: 30s
      retries: 5
      timeout: 5s
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: ${PG_PASS:?database password required}
      POSTGRES_USER: ${PG_USER:-authentik}
      POSTGRES_DB: ${PG_DB:-authentik}
    env_file:
      - .env

  redis:
    image: redis:7-alpine
    container_name: authentik-redis
    command: --save 60 1 --loglevel warning
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
      start_period: 20s
      interval: 30s
      retries: 5
      timeout: 3s
    volumes:
      - redisdata:/data

  server:
    image: ghcr.io/goauthentik/server:2026.5.1
    container_name: authentik-server
    restart: unless-stopped
    command: start
    environment:
      AUTHENTIK_REDIS__HOST: redis
      AUTHENTIK_POSTGRESQL__HOST: postgresql
      AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
      AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
      AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
    volumes:
      - ./media:/media
      - ./custom-templates:/templates
    env_file:
      - .env
    ports:
      - "${AUTHENTIK_PORT_HTTP:-9000}:9000"
      - "${AUTHENTIK_PORT_HTTPS:-9443}:9443"
    depends_on:
      postgresql:
        condition: service_healthy
      redis:
        condition: service_healthy

  worker:
    image: ghcr.io/goauthentik/server:2026.5.1
    container_name: authentik-worker
    restart: unless-stopped
    command: start_worker
    environment:
      AUTHENTIK_REDIS__HOST: redis
      AUTHENTIK_POSTGRESQL__HOST: postgresql
      AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
      AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
      AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
    user: root
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./media:/media
      - ./certs:/certs
      - ./custom-templates:/templates
    env_file:
      - .env
    depends_on:
      postgresql:
        condition: service_healthy
      redis:
        condition: service_healthy

volumes:
  pgdata:
    driver: local
  redisdata:
    driver: local
Enter fullscreen mode Exit fullscreen mode

Before running this configuration, we need to create a .env file in the same directory. This file will contain the encryption keys and database credentials. You can use the openssl rand -base64 36 command in your terminal to generate the AUTHENTIK_SECRET_KEY value.

PG_PASS=Generate_A_Secure_Password_Here
PG_USER=authentik
PG_DB=authentik
AUTHENTIK_PORT_HTTP=9000
AUTHENTIK_PORT_HTTPS=9443
AUTHENTIK_SECRET_KEY=your_secure_key_generated_with_openssl
AUTHENTIK_ERROR_REPORTING__ENABLED=false
Enter fullscreen mode Exit fullscreen mode

You can spin up the entire stack using the docker compose up -d command. It may take about a minute for the services to become fully ready. Navigate to http://<your-server-ip-address>:9000/if/flow/initial-setup/ in your browser to create your initial administrator account.


Reverse Proxy Integration: Nginx Proxy Manager and Authentik Forward Auth

The heart of the SSO system is the reverse proxy layer, which intercepts all incoming requests and asks Authentik, "Is this user logged in?" In my homelab environment, I use Nginx Proxy Manager (NPM) or direct raw Nginx for its flexibility and ease of management. To integrate the proxy with Authentik, we must choose between the Forward Auth or Proxy Provider methods.

The Forward Auth method works on the principle that Nginx sends an HTTP subrequest to Authentik's lightweight control endpoint (Outpost) on every request. If the user is authenticated, Nginx allows the request to proceed to the actual destination; if not, it redirects the user to the Authentik login page.

ℹ️ Nginx Configuration Detail

When defining a host on Nginx Proxy Manager, you need to go to the "Advanced" tab and add custom Nginx directives. These directives ensure that incoming traffic passes through the auth check.

Below is an example of a custom_locations or location block you can use for any service (e.g., Portainer) you want to protect on Nginx:

# Authentik Forward Auth Integration
location / {
    # The local service port where the user actually wants to go
    proxy_pass http://192.168.1.50:9000; 

    # Authentik Outpost redirection
    auth_request /outpost.goauthentik.io/auth/nginx;
    error_page 401 = @goauthentik_search_redirection;

    # Pass user information returned from the auth request to the service as headers
    auth_request_set $auth_cookie $upstream_http_set_cookie;
    add_header Set-Cookie $auth_cookie;

    auth_request_set $authentik_username $upstream_http_x_authentik_username;
    proxy_set_header X-Remote-User $authentik_username;
}

location = /outpost.goauthentik.io/auth/nginx {
    proxy_pass http://192.168.1.100:9000/outpost.goauthentik.io/auth/nginx;
    proxy_set_header Host $host;
    proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    # Save bandwidth by not sending the body in subrequests
    proxy_pass_request_body off;
    proxy_set_header Content-Length "";
}

location @goauthentik_search_redirection {
    return 302 $scheme://$http_host/outpost.goauthentik.io/start?rd=$request_uri;
}
Enter fullscreen mode Exit fullscreen mode

Putting Applications Behind SSO: Step-by-Step Integration Process

After spinning up the system and configuring the proxy, we need to define our applications in the Authentik panel. In the Authentik world, every protected service matches an Application and a Provider that defines how to access this application.

Term Description Homelab Example
Application The application card and name that the user will see Portainer, Grafana, Home Assistant
Provider The authentication protocol and authorization type OIDC, SAML, Forward Auth (Single App)
Outpost The service that processes proxy requests and validates tokens Authentik Embedded Outpost

The steps to add an application are as follows:

  1. Creating a Provider: In the Authentik Admin interface, navigate to Applications -> Providers. Click the "Create" button and select "Proxy Provider".
  2. Configuring Settings: Enter the Provider name. Select the default default-provider-authorization-implicit-consent flow as the "Authorization flow". Enter the full URL where the application will be accessed from the outside in the "External host" field (e.g., https://portainer.local.lan).
  3. Defining the Application: Go to the Applications -> Applications menu. Create a new application using the "Create with Provider" option. Bind the Provider you just created to this application.
  4. Updating the Outpost: Edit the "authentik Embedded Outpost" entry under the Applications -> Outposts tab and add your newly created application to this outpost's "Applications" list. If this step is skipped, proxy requests will return authorization errors (500 or 401).

Multi-Factor Authentication (MFA/2FA) Configuration and Security Hardening

If you really want to take your homelab's security to the next level, just a username and password are not enough. Authentik natively supports TOTP (Google Authenticator, Aegis, Bitwarden) and WebAuthn (Yubikey, Passkey, TouchID). I enforce TOTP usage for all my users (including family members).

To configure this, we will use the "Flows" mechanism in Authentik. Go to the Flows and Stages -> Flows tab. Find the default-authentication-flow flow. This flow defines the stages a user goes through when logging into the system.

⚠️ Critical Security Warning

Before enforcing MFA, make sure you create a "Recovery" flow or backup codes for yourself. Otherwise, if you lose your phone, you might lock yourself out of your own homelab system.

To enable mandatory MFA, follow these steps:

  1. Create a new "Authenticator Validation Stage" from Flows and Stages -> Stages. Select which 2FA methods you want to allow here (TOTP, WebAuthn).
  2. Go into the default-authentication-flow flow and click on the "Bindings" tab.
  3. Add the "Authenticator Validation Stage" you just created right after the username and password stages.
  4. Users will automatically be greeted with a QR code screen on their first login and will not be able to access the system without completing the 2FA setup.

Common Issues and Solutions in Homelab SSO Infrastructure

In my own deployments and based on feedback from others, I've seen that SSL certificates and redirect loops are the most time-consuming issues during SSO integration. If your browser is giving a "Too many redirects" error, make sure the X-Forwarded-Proto header is set correctly on Nginx.

# Add this to Nginx proxy blocks to prevent redirect loops
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
Enter fullscreen mode Exit fullscreen mode

Another common issue is Docker containers not being able to talk to each other. If your Authentik server and Nginx Proxy Manager are on different Docker networks, Forward Auth requests will result in a 502 Bad Gateway error. To solve this, you need to connect both services to a common Docker network (e.g., proxy-net):

# Create a shared network
docker network create proxy-net
Enter fullscreen mode Exit fullscreen mode

Then, define this network as external in the docker-compose.yml files of both services and add it under the services. This way, the services can access each other securely using container names instead of IP addresses.


Conclusion

With this guide, you have put complex password management behind you in your homelab environment and gathered all your services behind a single secure gateway. This Single Sign-On (SSO) in your Homelab setup that we built with Authentik will standardize your security template for every new service you add in the future. Now, you can securely manage your entire local network with just a single strong password and a physical security key (or TOTP app).

As a next step, you can try creating user groups in Authentik to grant your family members access to only specific services (e.g., only Jellyfin or Plex).

Top comments (0)