Skip to main content

Self-Host Authelia: Authentication Middleware 2026

·OSSAlt Team
autheliaauthentication2faself-hostingdockercaddynginx2026

TL;DR

Authelia (Apache 2.0, ~21K GitHub stars, Go) is authentication middleware that sits in front of your reverse proxy and adds login, 2FA, and access control to any self-hosted app. Unlike Authentik (which is a full identity provider), Authelia is focused purely on authentication — it's lighter, faster, and simpler to configure for the common use case of protecting your homelab behind a single sign-on portal. ~50MB RAM vs Authentik's ~500MB.

Key Takeaways

  • Authelia: Apache 2.0, ~21K stars, Go — auth middleware for Caddy/Nginx/Traefik
  • Minimal resources: ~50MB RAM — runs on the smallest VPS or Raspberry Pi
  • 2FA: TOTP (Google Authenticator), WebAuthn (hardware keys), push notifications
  • Access policies: Per-domain access rules — require different auth levels per app
  • LDAP support: Connect to LDAP/Active Directory for user management
  • vs Authentik: Authelia is simpler, lighter, and not an OAuth2 provider

Authelia vs Authentik

FeatureAutheliaAuthentik
RAM usage~50MB~500MB
Setup complexityLowMedium
Config styleYAML filesWeb UI
OAuth2/OIDC ProviderNoYes
SAML ProviderNoYes
Proxy authenticationYesYes (Outposts)
User managementYAML/LDAPWeb UI
Flow customizationNoYes
Best forSimple 2FA gatewayFull IdP with app management

Part 1: Docker Setup

# docker-compose.yml
services:
  authelia:
    image: authelia/authelia:latest
    container_name: authelia
    restart: unless-stopped
    volumes:
      - ./authelia/config:/config
      - authelia_data:/data
    ports:
      - "9091:9091"
    environment:
      TZ: America/Los_Angeles
    depends_on:
      - redis

  redis:
    image: redis:7-alpine
    container_name: authelia-redis
    restart: unless-stopped
    volumes:
      - redis_data:/data
    command: redis-server --save 60 1

volumes:
  authelia_data:
  redis_data:

Part 2: Configuration Files

Create the configuration directory and files:

mkdir -p ./authelia/config

Main configuration

# ./authelia/config/configuration.yml

server:
  host: 0.0.0.0
  port: 9091

log:
  level: info

# JWT secret for session tokens:
jwt_secret: "${JWT_SECRET}"   # openssl rand -hex 32

# Default redirection URL after login:
default_redirection_url: "https://yourdomain.com"

# Totp configuration:
totp:
  issuer: yourdomain.com

# Duo 2FA (optional):
# duo_api:
#   hostname: api-123456789.duosecurity.com
#   integration_key: XXXX
#   secret_key: XXXX

# File-based user database (simple setup):
authentication_backend:
  file:
    path: /config/users.yml
    password:
      algorithm: argon2id
      iterations: 3
      memory: 65536
      parallelism: 4

# Access control policies:
access_control:
  default_policy: deny

  rules:
    # Allow public access to specific paths:
    - domain: "public.yourdomain.com"
      policy: bypass

    # Require 1FA for general apps:
    - domain: "*.yourdomain.com"
      policy: one_factor

    # Require 2FA for sensitive apps:
    - domain: "vault.yourdomain.com"
      policy: two_factor
    - domain: "portainer.yourdomain.com"
      policy: two_factor
    - domain: "proxmox.yourdomain.com"
      policy: two_factor

# Session configuration:
session:
  name: authelia_session
  secret: "${SESSION_SECRET}"   # openssl rand -hex 32
  expiration: 1h
  inactivity: 5m
  remember_me_duration: 1M

  redis:
    host: redis
    port: 6379

# Storage (SQLite for simplicity):
storage:
  encryption_key: "${STORAGE_ENCRYPTION_KEY}"   # openssl rand -hex 32
  local:
    path: /data/db.sqlite3

# Notification (email):
notifier:
  smtp:
    username: authelia@yourdomain.com
    password: "${MAIL_PASSWORD}"
    host: mail.yourdomain.com
    port: 587
    sender: "Authelia <authelia@yourdomain.com>"
    tls:
      skip_verify: false

User database

# ./authelia/config/users.yml
# Generate password hash: docker run authelia/authelia:latest authelia crypto hash generate argon2 --password "yourpassword"

users:
  yourname:
    disabled: false
    displayname: "Your Name"
    password: "$argon2id$v=19$m=65536,t=3,p=4$..."   # hash from above
    email: you@yourdomain.com
    groups:
      - admins
      - users
  alice:
    disabled: false
    displayname: "Alice"
    password: "$argon2id$v=19$m=65536,t=3,p=4$..."
    email: alice@yourdomain.com
    groups:
      - users
# Generate a password hash:
docker run --rm authelia/authelia:latest \
  authelia crypto hash generate argon2 --password "yourpassword"

Part 3: HTTPS with Caddy

Caddy forwards authentication checks to Authelia:

# Authelia itself:
auth.yourdomain.com {
    reverse_proxy authelia:9091
}

# Snippet for protected apps:
(authelia) {
    forward_auth authelia:9091 {
        uri /api/verify?rd=https://auth.yourdomain.com
        copy_headers Remote-User Remote-Groups Remote-Name Remote-Email
        trusted_proxies private_ranges
    }
}

# Portainer (requires 2FA per policy):
portainer.yourdomain.com {
    import authelia
    reverse_proxy portainer:9000
}

# Grafana:
grafana.yourdomain.com {
    import authelia
    reverse_proxy grafana:3000
}

# Nextcloud (bypass Authelia — Nextcloud has its own auth):
nextcloud.yourdomain.com {
    reverse_proxy nextcloud:80
}

Part 4: Nginx Integration

For Nginx users:

# /etc/nginx/snippets/authelia-location.conf
location /authelia {
    internal;
    proxy_pass http://127.0.0.1:9091/api/verify;
    proxy_pass_request_body off;
    proxy_set_header Content-Length "";
    proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-Host $http_host;
    proxy_set_header X-Forwarded-Uri $request_uri;
    proxy_set_header X-Forwarded-Method $request_method;
    proxy_connect_timeout 5s;
    proxy_read_timeout 10s;
}
# Protected virtual host:
server {
    listen 443 ssl;
    server_name portainer.yourdomain.com;

    include /etc/nginx/snippets/ssl.conf;

    location / {
        auth_request /authelia;
        auth_request_set $target_url $scheme://$http_host$request_uri;
        auth_request_set $user $upstream_http_remote_user;
        auth_request_set $groups $upstream_http_remote_groups;
        error_page 401 =302 https://auth.yourdomain.com/?rd=$target_url;

        proxy_set_header Remote-User $user;
        proxy_set_header Remote-Groups $groups;
        proxy_pass http://portainer:9000;
    }
}

Part 5: 2FA Setup

TOTP (Authenticator apps)

  1. Log in to any protected app → redirected to Authelia
  2. Click Register device → Time-based One-Time Password
  3. Scan QR code with Google Authenticator, Aegis, or Bitwarden
  4. Enter code to confirm

WebAuthn (Hardware keys / Face ID / Touch ID)

  1. Log in → Register device → WebAuthn device
  2. Plug in YubiKey or use Face ID/Touch ID on supported devices
  3. Name the device → confirm

Disable 2FA for specific apps

# In configuration.yml:
access_control:
  rules:
    # Internal apps — no 2FA needed:
    - domain: "homeassistant.yourdomain.com"
      networks:
        - 192.168.1.0/24    # Local network only
      policy: bypass

    # Or: one_factor (password only, no TOTP):
    - domain: "jellyfin.yourdomain.com"
      policy: one_factor

Part 6: LDAP Backend

For larger setups, replace the file backend with LDAP:

# configuration.yml:
authentication_backend:
  ldap:
    implementation: custom    # or: activedirectory, lldap, glauth
    url: "ldap://lldap:3890"
    base_dn: "dc=yourdomain,dc=com"
    username_attribute: uid
    additional_users_dn: "ou=people"
    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=admin,dc=yourdomain,dc=com"
    password: "${LDAP_PASSWORD}"

Pair with lldap — a lightweight LDAP server designed specifically for self-hosted setups.


Part 7: Access Control Patterns

access_control:
  default_policy: deny

  rules:
    # 1. Bypass healthcheck endpoints:
    - domain: "*.yourdomain.com"
      resources:
        - "^/health$"
        - "^/metrics$"
      policy: bypass

    # 2. Local network gets 1FA only:
    - domain: "*.yourdomain.com"
      networks:
        - 192.168.1.0/24
        - 10.0.0.0/8
      policy: one_factor

    # 3. Admin group gets access to admin tools:
    - domain: "portainer.yourdomain.com"
      subject:
        - "group:admins"
      policy: two_factor

    # 4. Users group gets access to media:
    - domain: "jellyfin.yourdomain.com"
      subject:
        - "group:users"
        - "group:admins"
      policy: one_factor

    # 5. Block everyone else from everything:
    - domain: "*.yourdomain.com"
      policy: deny

Maintenance

# Update:
docker compose pull
docker compose up -d

# Validate configuration:
docker exec authelia authelia validate-config --config /config/configuration.yml

# Backup:
# SQLite database:
docker cp authelia:/data/db.sqlite3 authelia-backup-$(date +%Y%m%d).db

# Config files:
cp -r ./authelia/config ./authelia-config-backup-$(date +%Y%m%d)

# Logs:
docker compose logs -f authelia

# Test authentication flow:
curl -v "https://auth.yourdomain.com/api/health"

See also: Authentik — full identity provider with OAuth2/OIDC for apps that need programmatic authentication

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

Comments