Self-Host Authentik: Identity Provider and SSO 2026
TL;DR
Authentik (MIT, ~14K GitHub stars, Python/Django) is a comprehensive self-hosted identity provider. It provides SSO for all your self-hosted apps via OAuth2/OIDC, SAML 2.0, and a proxy authentication mode that protects apps that don't natively support authentication. Okta charges $2/user/month; Auth0 charges $23/month for 1,000 users. Authentik self-hosted is free for unlimited users and supports every major authentication protocol.
Key Takeaways
- Authentik: MIT, ~14K stars, Python — IdP with OAuth2, OIDC, SAML 2.0, LDAP, and proxy auth
- Proxy provider: Protect any app (even those without auth) behind Authentik's login
- Flows: Visual policy engine — customize login, enrollment, recovery, and MFA flows
- Outposts: Lightweight proxy components deployed next to apps for proxy authentication
- LDAP provider: Expose users as LDAP directory for legacy apps
- MFA: TOTP, WebAuthn (hardware keys), FIDO2 — all built in
Authentik vs Authelia vs Keycloak
| Feature | Authentik | Authelia | Keycloak |
|---|---|---|---|
| Admin UI | Excellent | Basic | Excellent |
| Proxy auth | Yes (Outposts) | Yes (built-in) | No |
| OAuth2/OIDC Provider | Yes | No | Yes |
| SAML Provider | Yes | No | Yes |
| LDAP Provider | Yes | No | Yes |
| Flow customization | Visual editor | Config file | Limited UI |
| RAM usage | ~500MB | ~50MB | ~512MB+ |
| Setup complexity | Medium | Simple | Complex |
| User management | Full UI | Config file | Full UI |
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}"]
interval: 5s
start_period: 20s
volumes:
- authentik_db:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: "${PG_PASS}"
POSTGRES_USER: authentik
POSTGRES_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"]
interval: 5s
volumes:
- authentik_redis:/data
server:
image: ghcr.io/goauthentik/server:latest
restart: unless-stopped
command: server
environment: &env
AUTHENTIK_REDIS__HOST: redis
AUTHENTIK_POSTGRESQL__HOST: postgresql
AUTHENTIK_POSTGRESQL__USER: authentik
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_POSTGRESQL__PASSWORD: "${PG_PASS}"
AUTHENTIK_SECRET_KEY: "${AUTHENTIK_SECRET_KEY}"
AUTHENTIK_EMAIL__HOST: mail.yourdomain.com
AUTHENTIK_EMAIL__PORT: 587
AUTHENTIK_EMAIL__USERNAME: authentik@yourdomain.com
AUTHENTIK_EMAIL__PASSWORD: "${MAIL_PASSWORD}"
AUTHENTIK_EMAIL__USE_TLS: "true"
AUTHENTIK_EMAIL__FROM: authentik@yourdomain.com
volumes:
- authentik_media:/media
- authentik_templates:/templates
ports:
- "9000:9000"
- "9443:9443"
depends_on:
- postgresql
- redis
worker:
image: ghcr.io/goauthentik/server:latest
restart: unless-stopped
command: worker
environment: *env
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- authentik_media:/media
- authentik_certs:/certs
- authentik_templates:/templates
depends_on:
- postgresql
- redis
volumes:
authentik_db:
authentik_redis:
authentik_media:
authentik_certs:
authentik_templates:
# Generate secrets:
echo "PG_PASS=$(openssl rand -base64 36)" >> .env
echo "AUTHENTIK_SECRET_KEY=$(openssl rand -base64 60)" >> .env
docker compose up -d
# Get initial admin password from logs (first run):
docker compose logs server | grep "Initial admin credentials"
Part 2: HTTPS with Caddy
auth.yourdomain.com {
reverse_proxy localhost:9000
}
Visit https://auth.yourdomain.com/if/flow/initial-setup/ → set admin password.
Part 3: OAuth2/OIDC Provider (Protect an App)
Protect an app that supports OAuth2/OIDC natively (Gitea, Nextcloud, Grafana, etc.):
Create an OAuth2 Provider
- Applications → Providers → Create → OAuth2/OpenID Provider
- Name:
Gitea - Authorization flow:
default-provider-authorization-explicit-consent - Client type: Confidential
- Copy: Client ID and Client Secret
- Redirect URIs:
https://gitea.yourdomain.com/user/oauth2/authentik/callback - Scopes:
openid,email,profile
Create an Application
- Applications → Applications → Create
- Name:
Gitea - Provider: select
Gitea(the provider you just created) - Launch URL:
https://gitea.yourdomain.com
Configure Gitea
# app.ini
[oauth2]
ENABLED = true
# Add via Gitea admin UI → Site Administration → Authentication Sources → Add
# Provider: OpenID Connect
# Client ID: from Authentik
# Client Secret: from Authentik
# Auto Discovery URL: https://auth.yourdomain.com/application/o/gitea/.well-known/openid-configuration
Part 4: Proxy Provider (Protect Any App)
The proxy provider protects apps that have no built-in authentication:
How it works
Browser → Caddy → Authentik Outpost (proxy) → Your App
↑ checks session with Authentik server
Create a Proxy Provider
- Applications → Providers → Create → Proxy Provider
- Name:
Portainer - Mode: Forward auth (single application)
- External host:
https://portainer.yourdomain.com - Internal host:
http://portainer:9000 - Skip path regex (optional):
/api/webhooks/.*
Create an Outpost
- Applications → Outposts → Create
- Name:
proxy-outpost - Type: Proxy
- Integration: Docker (uses the Docker socket)
- Applications: select all apps using proxy providers
Authentik automatically deploys the outpost container.
Configure Caddy for forward auth
portainer.yourdomain.com {
forward_auth localhost:9000 {
uri /outpost.goauthentik.io/auth/caddy
copy_headers X-Authentik-Username X-Authentik-Groups X-Authentik-Email
trusted_proxies private_ranges
}
reverse_proxy localhost:9000
}
Part 5: MFA Configuration
Enable TOTP (Authenticator apps)
- Flows & Stages → Stages → Create → Authenticator TOTP Stage
- Name:
totp-setup - Flows → default-authentication-flow → Edit
- Add stage binding:
totp-setupafter password stage
Enable WebAuthn (Hardware keys / biometrics)
- Flows & Stages → Stages → Create → Authenticator WebAuthn Stage
- Name:
webauthn-setup - Add to authentication flow
Require MFA for specific groups
# In your authentication flow, add a Policy Binding:
# - Expression policy that checks group membership:
return request.user.ak_groups.filter(name="Admins").exists()
Part 6: LDAP Provider
Expose Authentik users as an LDAP directory for legacy apps (Synology, older software):
- Applications → Providers → Create → LDAP Provider
- Name:
ldap - Bind DN:
cn=admin,dc=yourdomain,dc=com - Certificate: select your TLS cert
Create LDAP Outpost
- Applications → Outposts → Create
- Type: LDAP
- Port mapping:
389:3389(LDAP),636:6636(LDAPS)
Test LDAP connection
ldapsearch -H ldap://localhost:389 \
-D "cn=admin,dc=yourdomain,dc=com" \
-w "admin-password" \
-b "dc=yourdomain,dc=com" \
"(objectClass=person)"
Part 7: User Enrollment and Invitation
Self-registration flow
- Flows & Stages → Flows → Create
- Designation: Enrollment
- Stages: Email verification → User write → Username field → Password field
- Assign as:
default-user-settings-flow
Invitation links
Create single-use or multi-use invitation links:
- Directory → Invitations → Create
- Expires: optional date
- Copy invitation link → send to new user
Disable public registration
# In Authentik admin:
# Flows → default-user-settings-flow → Edit
# Remove "Public" from Flow permissions
# Or: Policies → Expression Policy → "return False" → bind to enrollment flow
Part 8: Backup and Restore
# Backup database:
docker exec authentik-postgresql-1 pg_dump -U authentik authentik \
| gzip > authentik-db-$(date +%Y%m%d).sql.gz
# Backup media (custom logos, etc.):
tar -czf authentik-media-$(date +%Y%m%d).tar.gz \
$(docker volume inspect authentik_authentik_media --format '{{.Mountpoint}}')
# Export configuration as YAML blueprint:
docker exec authentik-server-1 ak export_blueprint > authentik-blueprint-$(date +%Y%m%d).yaml
# Restore database:
gunzip < authentik-db-YYYYMMDD.sql.gz | \
docker exec -i authentik-postgresql-1 psql -U authentik authentik
# Logs:
docker compose logs -f server worker
See also: Authelia — simpler authentication middleware with lower resource requirements
See all open source security tools at OSSAlt.com/categories/security.