Authentik: Self-Hosted SSO and Identity Provider for Your Homelab

Authentik: Self-Hosted SSO and Identity Provider for Your Homelab

Why Your Homelab Needs a Real Identity Provider

You've got a dozen services scattered across your lab—Proxmox, Gitea, Paperless, Immich, your internal wiki. Each one has its own login form, its own password database, probably stored in plaintext in a Docker environment variable somewhere. You're the only user, so credentials don't feel like a security problem until they are. Authentik solves this by giving you enterprise-grade SSO (single sign-on) and identity management in your homelab, without the enterprise price tag or complexity.

This post covers a production-ready installation of Authentik 2024.12.2 on a single Docker host, configured with OAuth2 and LDAP backends so you can integrate it with everything from Grafana to your Synology NAS. I'm writing this for people who already run Docker Compose and want to replace multiple authentication systems with one authoritative source of truth.

Prerequisites and What You're Starting With

You need:

  • Docker 26.1+ and Docker Compose 2.20+ (the newer `docker compose` command, not `docker-compose`)
  • 2+ CPU cores and 2GB RAM minimum (4GB+ if you're actually using it, which you will be)
  • A domain or subdomain for Authentik (e.g., `auth.lab.local` or `authentik.example.com`)
  • A reverse proxy already in place—I'm using Traefik, but this works with Nginx, Caddy, or HAProxy
  • Familiarity with Docker volumes, networking, and environment variable management

I'm testing this on a Dell T5810 with 24GB RAM running Ubuntu 24.04.1 LTS, Docker 26.1.3, and a local Traefik 3.1 instance. Your exact hardware doesn't matter much—Authentik is lightweight until you hit thousands of users or LDAP sync events.

Deploying Authentik with Docker Compose

Authentik ships with an official Docker Compose file, but it's bloated for a homelab. Here's a lean, annotated version that includes PostgreSQL, Redis, and the Authentik server and worker in one stack.

version: '3.8'

services:
  postgresql:
    image: postgres:16-alpine
    container_name: authentik-db
    environment:
      POSTGRES_DB: authentik
      POSTGRES_USER: authentik
      POSTGRES_PASSWORD: your-strong-db-password-here
    volumes:
      - postgresql_data:/var/lib/postgresql/data
    networks:
      - authentik
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U authentik"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    container_name: authentik-cache
    command: redis-server --appendonly yes
    volumes:
      - redis_data:/data
    networks:
      - authentik
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  authentik-server:
    image: ghcr.io/goauthentik/authentik:2024.12.2
    container_name: authentik-server
    command: server
    environment:
      AUTHENTIK_REDIS__HOST: redis
      AUTHENTIK_POSTGRESQL__HOST: postgresql
      AUTHENTIK_POSTGRESQL__USER: authentik
      AUTHENTIK_POSTGRESQL__PASSWORD: your-strong-db-password-here
      AUTHENTIK_POSTGRESQL__NAME: authentik
      AUTHENTIK_SECRET_KEY: your-secret-key-min-50-chars-change-this-now
      AUTHENTIK_BOOTSTRAP_PASSWORD: bootstrapadmin123
      AUTHENTIK_BOOTSTRAP_TOKEN: your-bootstrap-token-here
    volumes:
      - ./media:/media
      - ./certs:/certs
    networks:
      - authentik
      - traefik
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.authentik.rule=Host(`auth.lab.local`)"
      - "traefik.http.routers.authentik.entrypoints=websecure"
      - "traefik.http.routers.authentik.tls=true"
      - "traefik.http.services.authentik.loadbalancer.server.port=9000"
    depends_on:
      postgresql:
        condition: service_healthy
      redis:
        condition: service_healthy
    restart: unless-stopped

  authentik-worker:
    image: ghcr.io/goauthentik/authentik:2024.12.2
    container_name: authentik-worker
    command: worker
    environment:
      AUTHENTIK_REDIS__HOST: redis
      AUTHENTIK_POSTGRESQL__HOST: postgresql
      AUTHENTIK_POSTGRESQL__USER: authentik
      AUTHENTIK_POSTGRESQL__PASSWORD: your-strong-db-password-here
      AUTHENTIK_POSTGRESQL__NAME: authentik
      AUTHENTIK_SECRET_KEY: your-secret-key-min-50-chars-change-this-now
    volumes:
      - ./media:/media
      - ./certs:/certs
    networks:
      - authentik
    depends_on:
      postgresql:
        condition: service_healthy
      redis:
        condition: service_healthy
    restart: unless-stopped

volumes:
  postgresql_data:
  redis_data:

networks:
  authentik:
    driver: bridge
  traefik:
    external: true

Critical gotcha #1: The `AUTHENTIK_SECRET_KEY` must be at least 50 characters and should be unique per deployment. Generate one with `openssl rand -base64 50`. If you change it after initial startup, Authentik will refuse to start because encrypted fields will become unreadable.

Save this as `docker-compose.yml` in a new directory (I use `/opt/authentik`), then bring it up:

cd /opt/authentik
docker compose up -d

Wait 30-60 seconds for the database migrations to complete. Check logs:

docker compose logs -f authentik-server

Once you see "INFO authentik.root: Starting authentik", it's ready. Navigate to your Authentik URL (e.g., `https://auth.lab.local`). You should see the login page.

Initial Setup and Admin Access

On first boot, Authentik creates an admin account using `AUTHENTIK_BOOTSTRAP_PASSWORD`. Log in with username `akadmin` and the password you set in the Compose file. Change this password immediately in the admin panel—it's a well-known default.

In the admin interface (at `/if/admin/`), you'll create users, groups, applications, and authentication policies. But before you set up OAuth providers for other apps, let's get LDAP working. LDAP is the lingua franca of homelabs—everything from your NAS to Grafana can bind to an LDAP directory.

Configuring LDAP for Homelab Integration

Authentik includes a built-in LDAP server. To enable it, go to Admin Panel → System → LDAP Server.

Enable LDAP and set the bind DN to something like `cn=admin,dc=lab,dc=local`. Set a bind password (this is what external services will use to authenticate). The port defaults to 3389 (not 389—Authentik avoids root ports).

Export the TLS certificate if you want LDAP over TLS (LDAPS on 6360). For a homelab, plaintext LDAP over your local network is acceptable if your network is trusted, but don't expose LDAP to the internet without TLS and firewall rules.

Test LDAP connectivity from your reverse proxy or another Docker container:

ldapsearch -x -H ldap://authentik-server:3389 \
  -D "cn=admin,dc=lab,dc=local" \
  -w your-bind-password \
  -b "cn=users,dc=lab,dc=local"

If you get a list of users, LDAP is working. If it hangs, check that the Authentik server is listening on 3389 and your network allows traffic.

Critical gotcha #2: The LDAP search base for users is `cn=users,dc=lab,dc=local`, not just `dc=lab,dc=local`. If you point Grafana or another app at the wrong base, it won't find users and will lock them out.

Creating OAuth2 Applications for Your Services

LDAP is for services that speak LDAP (Grafana, Proxmox, some NAS systems). OAuth2 is for web applications. Let's add Grafana as an OAuth2 client.

In Admin Panel → Applications → Applications, click Create. Set:

  • Name: Grafana
  • Slug: grafana
  • Provider: Create a new OAuth2 Provider

In the OAuth2 Provider dialog:

  • Name: Grafana
  • Client Type: Confidential
  • Redirect URIs: https://grafana.lab.local/login/generic_oauth
  • Scopes: openid email profile

Click Save and copy the Client ID and Client Secret. You'll need these in Grafana's config.

Now configure Grafana to use Authentik. In your Grafana Docker Compose or config file:

GF_AUTH_GENERIC_OAUTH_ENABLED: "true"
GF_AUTH_GENERIC_OAUTH_NAME: "Authentik"
GF_AUTH_GENERIC_OAUTH_CLIENT_ID: "your-client-id-from-authentik"
GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET: "your-client-secret-from-authentik"
GF_AUTH_GENERIC_OAUTH_SCOPES: "openid email profile"
GF_AUTH_GENERIC_OAUTH_AUTH_URL: "https://auth.lab.local/application/o/authorize/"
GF_AUTH_GENERIC_OAUTH_TOKEN_URL: "https://authentik-server:9000/application/o/token/"
GF_AUTH_GENERIC_OAUTH_API_URL: "https://auth.lab.local/application/o/userinfo/"
GF_AUTH_GENERIC_OAUTH_ALLOW_ASSIGN_GRAFANA_ADMIN: "false"

Restart Grafana and hit the login page. You should see an "Authentik" button. Click it, log in with your Authentik account, and Grafana will create or link a user automatically.

Common Issues and Troubleshooting

Issue: "CSRF token missing" on Authentik login page. Your reverse proxy isn't forwarding the original request host header. For Traefik, ensure you have `PassHostHeader: true` in the service config. For Nginx, check that you're forwarding `$http_host` and `X-Forwarded-Proto`.

Issue: OAuth2 redirect fails with "redirect_uri_mismatch". The Redirect URI in your Authentik application must match exactly—including protocol, domain, and path. `https://grafana.lab.local/login/generic_oauth` is different from `https://grafana.lab.local:443/login/generic_oauth`. Copy it directly from your app's documentation.

Issue: LDAP bind succeeds but users don't appear in searches. LDAP user sync in Authentik is a background task. Create a test user in Admin → Users, then check that it appears in the LDAP search. If the user exists but LDAP can't find it, you probably have the wrong search base.

Issue: Authentik won't start after a restart, complaining about the secret key. If you've modified `AUTHENTIK_SECRET_KEY` in the Compose file, Authentik will fail. Either revert the key to its original value or delete the PostgreSQL volume and start fresh (this wipes your data, so only do this in dev).

Next Steps: Policies and Access Control

With LDAP and OAuth2 working, you can now layer on access control policies. Authentik lets you define rules like "only allow access to Grafana from 192.168.x.x" or "require MFA for admin accounts." These go in Admin Panel → Policies, and you attach them to applications or flows.

For a homelab, I recommend:

  • One admin user account with MFA (Time-based OTP via Authentik's authenticator app)
  • One or more regular user accounts for household members or lab access
  • A "service account" group for API integrations (e.g., Telegraf monitoring)

You now have a self-hosted SSO provider running on your lab infrastructure. Every app you add can authenticate against Authentik, users get a single password to remember, and you've got an audit trail of who logged in where and when. That's something no quick-and-dirty per-app login system can give you.

For integrating additional services, check the Authentik documentation—there are pre-built provider templates for Nextcloud, Gitea, Plex, and dozens of others. The next logical step is setting up outpost deployments if you need to protect non-web applications or run authentication at the edge of your network.

Disclosure: This post contains affiliate links. If you purchase through these links, we may earn a small commission at no extra cost to you. We only recommend services we've tested and trust.

Read more