TL;DR — Quick Summary
Deploy Authelia as an SSO and 2FA authentication proxy. Covers Docker Compose, Traefik, TOTP, WebAuthn, LDAP backends, and OpenID Connect provider setup.
Authelia is an open-source authentication portal and authorization server that works as a forward-auth companion to your reverse proxy. Instead of adding login forms to every self-hosted service, Authelia intercepts every request, validates the session, and enforces 2FA policies — all from a single portal. This guide walks through a full production setup with Docker Compose, Traefik, TOTP, and an OpenID Connect provider.
Prerequisites
- Docker and Docker Compose v2 on a Linux host.
- A working Traefik v2/v3 reverse proxy (or Nginx/Caddy — integration notes included).
- A domain with DNS pointed at your server (Authelia requires a real domain for cookies).
- Basic knowledge of YAML and container networking.
Authelia Architecture
Authelia runs as a single binary or container. The request flow is:
- Browser requests
app.example.com. - Traefik intercepts and calls the ForwardAuth endpoint on
auth.example.com/api/authz/forward-auth. - Authelia checks the session cookie, applies access control rules, and returns
200 OKor401 Unauthorized. - On 401, Traefik redirects the user to the Authelia login portal.
- The user authenticates (password + 2FA), receives a signed session cookie, and is forwarded back.
Key components:
- Authentication portal — Login UI served at your Authelia subdomain.
- Session store — Redis for distributed/HA deployments; in-memory for single-node.
- Storage backend — SQLite (single-node) or PostgreSQL/MySQL for HA.
- Policy engine — Access control rules evaluated top-to-bottom.
Step 1: Docker Compose Stack
Create /opt/authelia/docker-compose.yml:
version: "3.9"
networks:
proxy:
external: true
services:
authelia:
image: authelia/authelia:latest
container_name: authelia
restart: unless-stopped
volumes:
- ./config:/config
environment:
- AUTHELIA_JWT_SECRET_FILE=/config/secrets/jwt_secret
- AUTHELIA_SESSION_SECRET_FILE=/config/secrets/session_secret
- AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE=/config/secrets/storage_key
networks:
- proxy
labels:
- "traefik.enable=true"
- "traefik.http.routers.authelia.rule=Host(`auth.example.com`)"
- "traefik.http.routers.authelia.entrypoints=websecure"
- "traefik.http.routers.authelia.tls.certresolver=letsencrypt"
- "traefik.http.middlewares.authelia.forwardauth.address=http://authelia:9091/api/authz/forward-auth"
- "traefik.http.middlewares.authelia.forwardauth.trustForwardHeader=true"
- "traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=Remote-User,Remote-Groups,Remote-Name,Remote-Email"
redis:
image: redis:7-alpine
container_name: authelia-redis
restart: unless-stopped
networks:
- proxy
volumes:
- redis-data:/data
volumes:
redis-data:
Generate secrets:
mkdir -p /opt/authelia/config/secrets
tr -dc 'A-Za-z0-9' </dev/urandom | head -c 64 > /opt/authelia/config/secrets/jwt_secret
tr -dc 'A-Za-z0-9' </dev/urandom | head -c 64 > /opt/authelia/config/secrets/session_secret
tr -dc 'A-Za-z0-9' </dev/urandom | head -c 64 > /opt/authelia/config/secrets/storage_key
Step 2: Core Configuration
Create /opt/authelia/config/configuration.yml:
server:
host: 0.0.0.0
port: 9091
log:
level: info
totp:
issuer: example.com
period: 30
skew: 1
webauthn:
timeout: 60s
display_name: "My Lab"
attestation_conveyance_preference: indirect
user_verification: preferred
authentication_backend:
file:
path: /config/users.yml
password:
algorithm: argon2id
iterations: 3
memory: 65536
parallelism: 4
key_length: 32
salt_length: 16
access_control:
default_policy: deny
rules:
- domain: "auth.example.com"
policy: bypass
- domain: "*.example.com"
subject: "group:admins"
policy: two_factor
- domain: "public.example.com"
policy: bypass
- domain: "internal.example.com"
networks:
- 192.168.1.0/24
policy: one_factor
session:
name: authelia_session
domain: example.com
same_site: lax
expiration: 1h
inactivity: 5m
remember_me: 1M
redis:
host: redis
port: 6379
regulation:
max_retries: 5
find_time: 2m
ban_time: 10m
storage:
encryption_key: "" # loaded from env
local:
path: /config/db.sqlite3
notifier:
smtp:
host: smtp.example.com
port: 587
username: noreply@example.com
password: yourpassword
sender: "Authelia <noreply@example.com>"
subject: "[Authelia] {title}"
startup_check_address: admin@example.com
Step 3: Authentication Backends
File-Based (users.yml)
users:
john:
displayname: "John Doe"
password: "$argon2id$v=19$m=65536,t=3,p=4$..."
email: john@example.com
groups:
- admins
- users
alice:
displayname: "Alice Smith"
password: "$argon2id$v=19$m=65536,t=3,p=4$..."
email: alice@example.com
groups:
- users
Generate argon2 hashes:
docker run authelia/authelia:latest authelia crypto hash generate argon2 --password 'yourpassword'
LDAP / Active Directory Backend
Replace the authentication_backend block in configuration.yml:
authentication_backend:
ldap:
address: ldap://ldap.example.com:389
implementation: activedirectory
base_dn: dc=example,dc=com
username_attribute: sAMAccountName
additional_users_dn: ou=Users
users_filter: "(&({username_attribute}={input})(objectClass=person))"
additional_groups_dn: ou=Groups
groups_filter: "(member={dn})"
group_name_attribute: cn
mail_attribute: mail
display_name_attribute: displayName
user: cn=authelia,ou=ServiceAccounts,dc=example,dc=com
password: ldappassword
For OpenLDAP, change implementation: activedirectory to implementation: custom and adjust filters accordingly.
Step 4: Access Control Deep Dive
Rules are evaluated top-to-bottom; first match wins. Fields:
| Field | Description | Example |
|---|---|---|
domain | Wildcard domain(s) | "*.example.com" |
resources | URL path regex | ["^/api/.*"] |
methods | HTTP methods | ["GET", "POST"] |
subject | User or group | "group:admins" |
networks | CIDR blocks | ["10.0.0.0/8"] |
policy | Action | one_factor, two_factor, bypass, deny |
Example — API bypass, admin portal requires 2FA, internal network requires 1FA:
access_control:
default_policy: deny
rules:
- domain: "api.example.com"
resources: ["^/public/.*"]
policy: bypass
- domain: "admin.example.com"
subject: "group:admins"
policy: two_factor
- domain: "intranet.example.com"
networks: ["192.168.0.0/16"]
policy: one_factor
- domain: "*.example.com"
policy: two_factor
Step 5: Two-Factor Methods
TOTP
Users enroll at the Authelia portal. Compatible with Google Authenticator, Authy, Bitwarden Authenticator, or any TOTP app. The totp.issuer name appears in the authenticator app.
WebAuthn / FIDO2 Passkeys
Supported in all modern browsers. Users register a hardware key (YubiKey, Titan) or platform authenticator (Face ID, Windows Hello). WebAuthn requires HTTPS and a matching RP ID (set to your session domain).
Duo Push
Add a duo_api block to configuration.yml:
duo_api:
hostname: api-XXXXXXXX.duosecurity.com
integration_key: DIXXXXXXXXXXXXXXXXXX
secret_key: your_duo_secret
Users receive push notifications on the Duo Mobile app.
Step 6: OpenID Connect Provider
Authelia can act as an OIDC IdP. Add an identity_providers block:
identity_providers:
oidc:
hmac_secret: a_random_secret_at_least_32_chars
issuer_private_key: |
-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----
clients:
- id: grafana
description: "Grafana Dashboard"
secret: "$argon2id$v=19$..."
public: false
authorization_policy: two_factor
consent_mode: implicit
redirect_uris:
- "https://grafana.example.com/login/generic_oauth"
scopes:
- openid
- profile
- email
- groups
grant_types:
- authorization_code
response_types:
- code
pkce_challenge_method: S256
Generate RSA key: openssl genrsa -out private.pem 4096
Reverse Proxy Integration
Nginx (auth_request)
location /authelia {
internal;
proxy_pass http://authelia:9091/api/authz/auth-request;
proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
}
location / {
auth_request /authelia;
auth_request_set $target_url $scheme://$http_host$request_uri;
error_page 401 =302 https://auth.example.com/?rd=$target_url;
proxy_pass http://upstream;
}
Caddy (forward_auth)
app.example.com {
forward_auth authelia:9091 {
uri /api/authz/forward-auth
copy_headers Remote-User Remote-Groups Remote-Name Remote-Email
}
reverse_proxy upstream:8080
}
HAProxy
Use the lua-http script from the Authelia docs to implement http-request lua.authelia-auth checks per backend.
Authelia vs Alternatives
| Feature | Authelia | Authentik | Keycloak | Vouch Proxy | OAuth2 Proxy | Pomerium |
|---|---|---|---|---|---|---|
| 2FA / MFA | TOTP, WebAuthn, Duo | TOTP, WebAuthn, Duo | TOTP, WebAuthn | Depends on IdP | Depends on IdP | Depends on IdP |
| OIDC Provider | Yes (v4.34+) | Yes (full IdP) | Yes (enterprise) | No | No | No |
| LDAP/AD | Yes | Yes | Yes | No | No | No |
| Access control | Built-in policy engine | Flow-based | Role-based | Minimal | Minimal | Policy-based |
| Storage | SQLite/PG/MySQL | PostgreSQL | PostgreSQL | None | None | None |
| Resource usage | ~50 MB RAM | ~300 MB RAM | ~512 MB RAM | ~10 MB | ~10 MB | ~50 MB |
| Complexity | Low-Medium | Medium | High | Low | Low | Medium |
| Self-hosted | Yes | Yes | Yes | Yes | Yes | Yes |
Brute Force Protection and Regulation
The regulation block bans IPs after failed login attempts:
regulation:
max_retries: 5 # failed attempts before ban
find_time: 2m # window to count failures
ban_time: 10m # ban duration
Failed attempts are stored in the database. Combined with Redis session storage, regulation works correctly across multiple Authelia replicas.
High Availability Storage
For multi-replica deployments, switch from SQLite to PostgreSQL:
storage:
postgres:
host: postgres
port: 5432
database: authelia
schema: public
username: authelia
password: dbpassword
Redis is already configured for distributed sessions. Run at least 2 Authelia replicas behind a load balancer for zero-downtime upgrades.
Troubleshooting
| Problem | Solution |
|---|---|
| 401 loop after login | Check session.domain matches the cookie domain for all protected services |
| 2FA enrollment email not received | Test SMTP with startup_check_address; check notifier logs |
| WebAuthn registration fails | Ensure HTTPS is active; RP ID must match session domain exactly |
| LDAP auth fails | Enable log.level: debug; verify bind DN credentials and base DN |
| OIDC consent screen repeats | Set consent_mode: implicit or pre-configured on the client |
| Regulation banning legitimate users | Check regulation.find_time; increase max_retries if shared IPs |
Summary
- Authelia is a lightweight SSO + 2FA proxy with a built-in policy engine — no per-app configuration changes.
- Forward-auth pattern means any reverse proxy can enforce authentication without touching upstream apps.
- TOTP, WebAuthn, and Duo give users flexible 2FA options.
- OIDC provider lets Authelia act as a full IdP for apps supporting OAuth2.
- Regulation and brute force protection are built-in — no separate fail2ban rules needed.
- Redis + PostgreSQL enables high-availability multi-replica deployments.