Skip to main content

How to Self-Host Authentik: SSO Identity Provider for Self-Hosted Apps 2026

·OSSAlt Team
authentikssoidentityoauth2oidcself-hostingdocker2026

TL;DR

Authentik (MIT, ~13K GitHub stars, Python/Go) is the best self-hosted identity provider for homelabs and small teams — a free alternative to Okta or Auth0 for your self-hosted apps. It supports OAuth2, OIDC, SAML, and LDAP, letting you add single sign-on to Grafana, Mattermost, Gitea, Nextcloud, Portainer, and any app that supports these protocols. One login for everything. Okta costs $2/user/month; Authentik is free for unlimited users and applications.

Key Takeaways

  • Authentik: MIT, ~13K stars — SSO/IdP for all your self-hosted apps
  • Protocols: OAuth2, OIDC, SAML 2.0, LDAP, RADIUS — works with any app
  • Flows: Highly customizable login flows (MFA, invite-only, staged enrollment)
  • Proxy provider: Add auth to apps that have no built-in auth (basic HTTP auth)
  • Pre-built integrations: Step-by-step guides for 50+ popular apps
  • LDAP outpost: Acts as LDAP server so apps that only support LDAP still work

Authentik vs Keycloak vs Zitadel

FeatureAuthentikKeycloakZitadel
LicenseMITApache 2.0Apache 2.0
GitHub Stars~13K~23K~8K
LanguagePython + GoJavaGo
UI/UXModern, cleanComplexModern
Setup complexityMediumHighMedium
Proxy authYesNoNo
LDAP outpostYesYesNo
PasswordlessYesYesYes
Flows customizationYes (visual)Yes (complex)Limited
RAM~400MB~600MB+~200MB

Part 1: Docker Setup

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

  redis:
    image: docker.io/library/redis:alpine
    command: --save 60 1 --loglevel warning
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
      start_period: 20s
    volumes:
      - redis:/data

  server:
    image: ghcr.io/goauthentik/server:2024.12.1
    restart: unless-stopped
    command: server
    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}
      AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
      AUTHENTIK_EMAIL__HOST: ${EMAIL_HOST:-localhost}
      AUTHENTIK_EMAIL__PORT: ${EMAIL_PORT:-25}
      AUTHENTIK_EMAIL__USERNAME: ${EMAIL_USERNAME:-""}
      AUTHENTIK_EMAIL__PASSWORD: ${EMAIL_PASSWORD:-""}
      AUTHENTIK_EMAIL__USE_TLS: ${EMAIL_USE_TLS:-false}
      AUTHENTIK_EMAIL__USE_SSL: ${EMAIL_USE_SSL:-false}
      AUTHENTIK_EMAIL__FROM: ${EMAIL_FROM:-authentik@localhost}
    volumes:
      - ./media:/media
      - ./custom-templates:/templates
    ports:
      - "${COMPOSE_PORT_HTTP:-9000}:9000"
      - "${COMPOSE_PORT_HTTPS:-9443}:9443"
    depends_on:
      - postgresql
      - redis

  worker:
    image: ghcr.io/goauthentik/server:2024.12.1
    restart: unless-stopped
    command: 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}
      AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./media:/media
      - ./custom-templates:/templates
    depends_on:
      - postgresql
      - redis

volumes:
  database:
  redis:
# .env
PG_PASS=$(openssl rand -hex 32)
AUTHENTIK_SECRET_KEY=$(openssl rand -hex 60)
EMAIL_HOST=smtp.yourdomain.com
EMAIL_PORT=587
EMAIL_USERNAME=noreply@yourdomain.com
EMAIL_PASSWORD=your-smtp-password
EMAIL_USE_TLS=true
EMAIL_FROM=noreply@yourdomain.com

docker compose up -d

Visit https://your-server:9443/if/flow/initial-setup/ to create the admin account.


Part 2: HTTPS with Caddy

auth.yourdomain.com {
    reverse_proxy localhost:9000
}

Part 3: Connect an App (Grafana Example)

Create an OIDC Provider in Authentik

  1. Applications → Providers → Create → OAuth2/OpenID Provider
  2. Name: Grafana
  3. Authorization flow: default-provider-authorization-explicit-consent
  4. Client type: Confidential
  5. Copy: Client ID and Client Secret
  6. Redirect URIs: https://grafana.yourdomain.com/login/generic_oauth
  7. Scopes: email, profile, openid

Create an Application

  1. Applications → Applications → Create
  2. Name: Grafana
  3. Slug: grafana
  4. Provider: Select the Grafana provider you just created
  5. Launch URL: https://grafana.yourdomain.com

Configure Grafana

# grafana.ini
[auth.generic_oauth]
enabled = true
name = Authentik
allow_sign_up = true
client_id = YOUR_CLIENT_ID
client_secret = YOUR_CLIENT_SECRET
scopes = openid email profile
auth_url = https://auth.yourdomain.com/application/o/grafana/authorize/
token_url = https://auth.yourdomain.com/application/o/grafana/token/
api_url = https://auth.yourdomain.com/application/o/userinfo/
role_attribute_path = contains(groups[*], 'authentik Admins') && 'Admin' || 'Viewer'

Part 4: Connect Other Apps

Mattermost

Provider type: OAuth2/OpenID → Redirect URI: https://chat.yourdomain.com/signup/gitlab/complete

In Mattermost System Console → OAuth 2.0 → GitLab (compatible):

Auth endpoint: https://auth.yourdomain.com/application/o/authorize/
Token endpoint: https://auth.yourdomain.com/application/o/token/
User API endpoint: https://auth.yourdomain.com/application/o/userinfo/

Gitea

  1. Authentik: Create OAuth2 Provider → Redirect URI: https://git.yourdomain.com/user/oauth2/authentik/callback
  2. Gitea: Site Admin → Authentication Sources → Add OAuth2 Source
  3. Provider: OpenID Connect
  4. Discovery URL: https://auth.yourdomain.com/application/o/gitea/.well-known/openid-configuration

Nextcloud

  1. Authentik: Create OIDC Provider → Redirect URI: https://cloud.yourdomain.com/apps/oidc_login/oidc
  2. Nextcloud: Apps → Install "OpenID Connect Login"
  3. Configure with Authentik OIDC endpoints

Portainer

  1. Authentik: Create OAuth2 Provider → Redirect URI: https://portainer.yourdomain.com
  2. Portainer: Settings → Authentication → OAuth
  3. Type: Custom, enter Authentik authorization/token/userinfo endpoints

Part 5: Proxy Provider (Add Auth to Any App)

The Proxy Provider adds authentication to apps with no built-in auth:

  1. Providers → Create → Proxy Provider
  2. Name: Unprotected App
  3. External host: https://app.yourdomain.com
  4. Mode: Forward auth (single application) or Forward auth (domain level)
# Caddy config for proxy auth:
app.yourdomain.com {
    forward_auth localhost:9000 {
        uri /outpost.goauthentik.io/auth/caddy

        copy_headers X-authentik-username X-authentik-groups X-authentik-email X-authentik-name X-authentik-uid

        trusted_proxies private_ranges
    }
    reverse_proxy localhost:8080
}

Now app.yourdomain.com requires Authentik login — even if the app has no auth.


Part 6: Multi-Factor Authentication

Configure MFA for all users:

  1. Flows → default-authentication-flow → Edit
  2. Add stage: MFA Validation Stage
  3. Configure: TOTP, WebAuthn (passkey), or email OTP

Users enroll MFA at: https://auth.yourdomain.com/if/user/ → Security


Part 7: LDAP Outpost

For apps that only support LDAP (not OIDC/SAML):

  1. Applications → Outposts → Create → LDAP Outpost
  2. Configure the outpost with the Authentik server URL
  3. Deploy the LDAP outpost:
services:
  authentik-ldap:
    image: ghcr.io/goauthentik/ldap:2024.12.1
    restart: unless-stopped
    ports:
      - "389:3389"   # LDAP
      - "636:6636"   # LDAPS
    environment:
      AUTHENTIK_HOST: https://auth.yourdomain.com
      AUTHENTIK_INSECURE: "false"
      AUTHENTIK_TOKEN: your-outpost-token

Apps connect to ldap://your-server:389 — Authentik handles authentication.


Maintenance

# Update Authentik (use pinned version tags):
# Edit docker-compose.yml to bump version number
docker compose pull
docker compose up -d

# Backup:
docker exec postgresql pg_dump -U authentik authentik \
  | gzip > authentik-db-$(date +%Y%m%d).sql.gz

# Logs:
docker compose logs -f server
docker compose logs -f worker

# Admin panel: https://auth.yourdomain.com/if/admin/

See all open source identity and authentication tools at OSSAlt.com/categories/security.

Comments