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:

  1. Browser requests app.example.com.
  2. Traefik intercepts and calls the ForwardAuth endpoint on auth.example.com/api/authz/forward-auth.
  3. Authelia checks the session cookie, applies access control rules, and returns 200 OK or 401 Unauthorized.
  4. On 401, Traefik redirects the user to the Authelia login portal.
  5. 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:

FieldDescriptionExample
domainWildcard domain(s)"*.example.com"
resourcesURL path regex["^/api/.*"]
methodsHTTP methods["GET", "POST"]
subjectUser or group"group:admins"
networksCIDR blocks["10.0.0.0/8"]
policyActionone_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

FeatureAutheliaAuthentikKeycloakVouch ProxyOAuth2 ProxyPomerium
2FA / MFATOTP, WebAuthn, DuoTOTP, WebAuthn, DuoTOTP, WebAuthnDepends on IdPDepends on IdPDepends on IdP
OIDC ProviderYes (v4.34+)Yes (full IdP)Yes (enterprise)NoNoNo
LDAP/ADYesYesYesNoNoNo
Access controlBuilt-in policy engineFlow-basedRole-basedMinimalMinimalPolicy-based
StorageSQLite/PG/MySQLPostgreSQLPostgreSQLNoneNoneNone
Resource usage~50 MB RAM~300 MB RAM~512 MB RAM~10 MB~10 MB~50 MB
ComplexityLow-MediumMediumHighLowLowMedium
Self-hostedYesYesYesYesYesYes

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

ProblemSolution
401 loop after loginCheck session.domain matches the cookie domain for all protected services
2FA enrollment email not receivedTest SMTP with startup_check_address; check notifier logs
WebAuthn registration failsEnsure HTTPS is active; RP ID must match session domain exactly
LDAP auth failsEnable log.level: debug; verify bind DN credentials and base DN
OIDC consent screen repeatsSet consent_mode: implicit or pre-configured on the client
Regulation banning legitimate usersCheck 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.