Authentik — SSO & Forward Auth in Docker

Every service in the stack — Radarr, Sonarr, Prowlarr, SABnzbd, qBittorrent, the Traefik dashboard — sits behind Authentik. You log in once with a username, password, and TOTP code, and that session covers everything. No per-service logins, no basic auth pop-ups, no storing credentials in browser password managers for a dozen different tools.

Users
Single sign-on
One login, one session · TOTP enforced

Traefik
Forward auth middleware
Checks every request · Redirects to Authentik if unauth

login request
forward auth

Authentik
  • SSO identity provider
  • Forward auth via Traefik
  • TOTP enforced on all users
Port: 9000

access granted
session data

Protected Services
Radarr · Sonarr · Prowlarr
Accessible after SSO login · Session shared across apps

PostgreSQL
User & session store
postgres:16-alpine · Persists all auth data


How forward auth works

Authentik doesn’t sit inline between the browser and your services. Instead it uses forward authentication via Traefik:

  1. A request arrives at Traefik for radarr.yourdomain.com
  2. Traefik calls Authentik’s forward auth endpoint before passing the request on
  3. Authentik checks the session cookie — if valid, it returns 200 and Traefik forwards the request to Radarr
  4. If there’s no valid session, Authentik returns 401 and Traefik redirects the browser to the Authentik login page
  5. After login (password + TOTP), Authentik sets a session cookie for .yourdomain.com — covering every subdomain at once

Authentik login page shown after being redirected from a protected app

The result: one login page, one session cookie, every service protected.


What you need before you start

  • Traefik already running — Authentik plugs in as a middleware
  • A subdomain for Authentik itself, e.g. authentik.yourdomain.com, routed through your Cloudflare tunnel to port 9000
  • A strong secret key — generate one with openssl rand -base64 36
  • A strong PostgreSQL password — generate one with openssl rand -base64 24

Docker Compose

Create /mnt/tank/stacks/authentik/compose.yaml:

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

  server:
    image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG:-2026.5.3}
    container_name: authentik-server
    command: server
    restart: unless-stopped
    depends_on:
      postgresql:
        condition: service_healthy
    env_file:
      - .env
    environment:
      AUTHENTIK_POSTGRESQL__HOST: postgresql
      AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
      AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
      AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
      AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
      AUTHENTIK_LISTEN__TRUSTED_PROXY_CIDRS: "0.0.0.0/0"
      AUTHENTIK_HOST: "https://authentik.yourdomain.com"
      AUTHENTIK_COOKIE_DOMAIN: "yourdomain.com"
    ports:
      - "9000:9000"
      - "9443:9443"
    volumes:
      - /mnt/tank/configs/authentik/data:/data
      - /mnt/tank/configs/authentik/templates:/templates
    shm_size: 512mb

  worker:
    image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG:-2026.5.3}
    container_name: authentik-worker
    command: worker
    restart: unless-stopped
    user: root
    depends_on:
      postgresql:
        condition: service_healthy
    env_file:
      - .env
    environment:
      AUTHENTIK_POSTGRESQL__HOST: postgresql
      AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
      AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
      AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
      AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
    volumes:
      - /mnt/tank/configs/authentik/data:/data
      - /mnt/tank/configs/authentik/certs:/certs
      - /mnt/tank/configs/authentik/templates:/templates
    shm_size: 512mb

volumes:
  database:
    driver: local

Create the .env file in the same directory:

PG_USER=authentik
PG_DB=authentik
PG_PASS=your-strong-postgres-password
AUTHENTIK_SECRET_KEY=your-strong-secret-key
AUTHENTIK_TAG=2026.5.3

Create the config directories and bring it up:

mkdir -p /mnt/tank/configs/authentik/{data,certs,templates}
cd /mnt/tank/stacks/authentik
docker compose up -d
docker compose logs -f server

Authentik will be available at http://<nas-ip>:9000 once the server and PostgreSQL are both healthy — usually around 60 seconds on first boot.

AUTHENTIK_COOKIE_DOMAIN is the key setting for SSO across subdomains. Setting it to yourdomain.com (without the leading dot) means the session cookie is valid for every subdomain — log in once at authentik.yourdomain.com and that session covers radarr.yourdomain.com, sonarr.yourdomain.com, and everything else.


First-time setup

1 — Create the admin account

Visit http://<nas-ip>:9000/if/flow/initial-setup/. Set your admin email and password. This creates the default admin user (akadmin).

Authentik initial setup screen for creating the admin account

2 — Deploy the Embedded Outpost

Go to Applications → Outposts. You’ll see a default authentik Embedded Outpost already listed. Edit it and make sure the Integration URL is set to http://authentik-server:9000 and it’s set to type Proxy. Save and deploy.

The outpost is what handles the actual forward auth requests from Traefik — it runs inside the Authentik server container.

Authentik New Outpost dialog with type set to Proxy

3 — Create a Proxy Provider

Go to Applications → Providers → Create and choose Proxy Provider.

  • Name: e.g. radarr-proxy
  • Authentication flow: default-authentication-flow
  • Authorisation flow: default-provider-authorization-implicit-consent
  • Forward auth (domain level): select this
  • Cookie domain: yourdomain.com

Using domain-level forward auth means one provider covers every subdomain — you don’t need a separate provider per service.

Authentik Create New Provider dialog with Forward auth (domain level) selected

4 — Create an Application

Go to Applications → Applications → Create:

  • Name: anything descriptive
  • Slug: e.g. homelab
  • Provider: the proxy provider you just created

Then go back to Outposts, edit the embedded outpost, and add this application to it.

Authentik New Application dialog configuring name, slug, and policy engine mode

5 — Wire up Traefik

Add the Authentik middleware to your Traefik dynamic.yml:

http:
  middlewares:
    authentik:
      forwardAuth:
        address: "http://127.0.0.1:9000/outpost.goauthentik.io/auth/traefik"
        trustForwardHeader: true
        maxResponseBodySize: 10240
        authResponseHeaders:
          - X-authentik-username
          - X-authentik-groups
          - X-authentik-email
          - X-authentik-name
          - X-authentik-uid
          - X-authentik-jwt

Then attach it to any router you want to protect:

  routers:
    radarr:
      rule: "Host(`radarr.yourdomain.com`)"
      entryPoints: [web]
      service: radarr
      middlewares:
        - authentik

Traefik hot-reloads — save the file and the route is protected immediately.

6 — Enforce TOTP

Go to Flows → Flows and edit default-authentication-flow. Add a stage:

  • Click Stage Bindings → Bind existing stage
  • Add default-authenticator-totp-setup — this prompts users to set up an authenticator app on first login
  • Add default-authenticator-validate — this enforces the TOTP check on every login

Order matters: place the TOTP validate stage after the password stage. Users who haven’t set up TOTP yet will be prompted to do so on their next login.

Authentik Set up Two-Factor authentication screen with QR code enrollment

7 — Optional: log in with Plex

Authentik can use your Plex account as a login source — useful if you want to let Plex users (yourself, or friends you’ve shared your server with) sign in without you creating them a separate password.

Go to Directory → Federation & Social login → Sources → Create and choose Plex Source:

  • Name / Slug: anything descriptive, e.g. plex
  • Authentication flow: default-authentication-flow
  • Enrollment flow: default-source-enrollment (creates an Authentik user automatically the first time someone logs in via Plex)
  • User matching mode: pick how a Plex login should map to an Authentik user — match by email is the common choice
  • Allow friends to authenticate: enable this if you want people who have access to your Plex server as a “friend” (not just you, the server owner) to be able to log in — leave it off to restrict login to your own Plex account only
  • Allowed servers: optionally restrict which of your Plex servers count for matching, if you have more than one

After creating the source, Authentik needs you to actually authorize it against your Plex account — it walks you through a plex.tv authorization popup the first time, then stores the resulting token against the source.

Finally, add the source to your login page: edit default-authentication-identification (Flows & Stages → Stages), and add your new Plex source under its Sources setting. A “Log in with Plex” button will now show up on the login screen alongside the normal username/password form.

Plex login is an alternative front door, not a replacement for TOTP. Users who authenticate via Plex still go through whatever stages are bound to default-authentication-flow — if you’ve enforced TOTP there (Step 6), a Plex-authenticated user will still be prompted to set up or validate their authenticator app.


Adding a new protected service

Once the middleware is in Traefik and the outpost is deployed, protecting a new service is a single line in dynamic.yml:

    new-service:
      rule: "Host(`new-service.yourdomain.com`)"
      entryPoints: [web]
      service: new-service
      middlewares:
        - authentik

No changes needed in Authentik itself — the domain-level proxy provider covers every subdomain automatically.


Useful commands

# Check all three containers are healthy
docker compose ps

# Stream server logs
docker compose logs -f server

# Stream worker logs (handles background tasks)
docker compose logs -f worker

# Reset akadmin password if locked out
docker exec authentik-server ak create_recovery_key 1 akadmin

Common problems

Problem Likely cause
Login page loops or redirects infinitely AUTHENTIK_HOST doesn’t match the URL you’re accessing, or cookie domain is wrong
502 from Traefik on protected services Authentik server not reachable at 127.0.0.1:9000 — check it’s running and on host network or correct port
Session not shared across subdomains AUTHENTIK_COOKIE_DOMAIN not set, or set with a leading dot — remove the dot
TOTP not being enforced Stage order in the authentication flow is wrong — validate must come after password
Outpost shows as disconnected Integration URL wrong, or Authentik server container name doesn’t match
Worker container keeps restarting PostgreSQL not healthy yet — check postgres logs, wait for healthcheck to pass
Avatar photo

By admin

Leave a Reply

Your email address will not be published. Required fields are marked *