How to Self-Host Authentik: SSO Identity Provider for Self-Hosted Apps 2026
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
| Feature | Authentik | Keycloak | Zitadel |
|---|---|---|---|
| License | MIT | Apache 2.0 | Apache 2.0 |
| GitHub Stars | ~13K | ~23K | ~8K |
| Language | Python + Go | Java | Go |
| UI/UX | Modern, clean | Complex | Modern |
| Setup complexity | Medium | High | Medium |
| Proxy auth | Yes | No | No |
| LDAP outpost | Yes | Yes | No |
| Passwordless | Yes | Yes | Yes |
| Flows customization | Yes (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
- Applications → Providers → Create → OAuth2/OpenID Provider
- Name:
Grafana - Authorization flow:
default-provider-authorization-explicit-consent - Client type: Confidential
- Copy: Client ID and Client Secret
- Redirect URIs:
https://grafana.yourdomain.com/login/generic_oauth - Scopes:
email,profile,openid
Create an Application
- Applications → Applications → Create
- Name:
Grafana - Slug:
grafana - Provider: Select the Grafana provider you just created
- 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
- Authentik: Create OAuth2 Provider → Redirect URI:
https://git.yourdomain.com/user/oauth2/authentik/callback - Gitea: Site Admin → Authentication Sources → Add OAuth2 Source
- Provider: OpenID Connect
- Discovery URL:
https://auth.yourdomain.com/application/o/gitea/.well-known/openid-configuration
Nextcloud
- Authentik: Create OIDC Provider → Redirect URI:
https://cloud.yourdomain.com/apps/oidc_login/oidc - Nextcloud: Apps → Install "OpenID Connect Login"
- Configure with Authentik OIDC endpoints
Portainer
- Authentik: Create OAuth2 Provider → Redirect URI:
https://portainer.yourdomain.com - Portainer: Settings → Authentication → OAuth
- 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:
- Providers → Create → Proxy Provider
- Name:
Unprotected App - External host:
https://app.yourdomain.com - 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:
- Flows → default-authentication-flow → Edit
- Add stage: MFA Validation Stage
- 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):
- Applications → Outposts → Create → LDAP Outpost
- Configure the outpost with the Authentik server URL
- 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.