How to Set Up Authelia: Self-Hosted SSO and 2FA for Your Services

autheliaself-hosted SSO2FAsingle sign-on

How to Set Up Authelia: Self-Hosted SSO and 2FA for Your Services

You have a growing homelab. Jellyfin for media, Nextcloud for files, Gitea for code, Grafana for monitoring, and a dozen other services behind a reverse proxy. Each one has its own login page, its own password, and its own session management. You are juggling credentials across services, and none of them have two-factor authentication unless the individual app supports it.

Authelia fixes this. It is a self-hosted authentication and authorization server that sits in front of your services, providing a single login page with two-factor authentication. Log in once, and you are authenticated across every protected service. It integrates with Traefik, Nginx, HAProxy, and Caddy through forward authentication middleware, and it supports TOTP, WebAuthn/passkeys, and push notifications for the second factor.

This guide walks you through a complete Authelia deployment: from Docker Compose setup to user management, 2FA configuration, reverse proxy integration, and access control policies. By the end, every service in your homelab will be behind a unified authentication layer with enforced 2FA.

Table of Contents

TL;DR

  • Authelia provides SSO and 2FA for any web service accessible through a reverse proxy.
  • It works via forward authentication: your reverse proxy asks Authelia “is this user allowed?” before serving the request.
  • File-based user management is simplest for homelabs with 1-5 users. LDAP is better for larger setups.
  • Traefik integration uses middleware labels on each container. Nginx integration uses auth_request directives.
  • 2FA supports TOTP (Google Authenticator, Authy), WebAuthn (YubiKey, passkeys), and Duo push notifications.
  • The entire stack runs in Docker with less than 200 MB of RAM.

What Authelia Does and How It Works

Authelia is a forward authentication server. Here is the request flow:

  1. A user visits https://grafana.yourdomain.com.
  2. The reverse proxy (Traefik or Nginx) intercepts the request and asks Authelia: “Is this user authenticated?”
  3. If not authenticated, Authelia redirects the user to https://auth.yourdomain.com — the Authelia login portal.
  4. The user enters their username and password.
  5. Authelia prompts for a second factor (TOTP code, WebAuthn key, or Duo push).
  6. After successful authentication, Authelia sets a session cookie and redirects the user back to the original URL.
  7. On subsequent requests, the reverse proxy checks Authelia again, but this time the session cookie is valid, so the request passes through immediately.

The key insight is that Authelia never sees the actual traffic to your services. It only handles authentication decisions. Your reverse proxy is the enforcement point. This means Authelia works with any web service, regardless of whether that service has its own authentication system.

Prerequisites

Before starting, you need:

  • A domain name with DNS pointing to your server (e.g., yourdomain.com with a wildcard *.yourdomain.com pointing to your IP).
  • Docker and Docker Compose installed on your server.
  • A reverse proxy — this guide covers Traefik and Nginx. If you do not have one yet, the Traefik section includes a complete configuration.
  • SMTP credentials for sending email notifications (password resets, 2FA registration). Any SMTP provider works: Gmail, Mailgun, Amazon SES, or a self-hosted mail server.

Architecture Overview

Internet
    |
    v
[Reverse Proxy: Traefik / Nginx]
    |
    |--- Forward Auth ---> [Authelia] ---> [Redis (sessions)]
    |                          |
    |                          +-------> [User DB (file or LDAP)]
    |
    +--- Proxied traffic ---> [Grafana]
    +--- Proxied traffic ---> [Nextcloud]
    +--- Proxied traffic ---> [Jellyfin]
    +--- Proxied traffic ---> [Any other service]

Authelia stores sessions in Redis for performance and persistence across restarts. User credentials are stored either in a YAML file (file-based backend) or in an LDAP directory (for larger deployments). Authelia also uses a small database (SQLite for single-instance, PostgreSQL for HA) to store TOTP secrets, WebAuthn registrations, and authentication logs.

Step 1: Directory Structure and File Layout

Create the directory structure for your Authelia deployment:

mkdir -p /opt/authelia/{config,data}

By the end of this guide, your directory will look like this:

/opt/authelia/
├── config/
│   ├── configuration.yml    # Main Authelia config
│   └── users_database.yml   # User credentials (file-based auth)
├── data/                    # SQLite database, generated at runtime
└── docker-compose.yml       # Full stack definition

Step 2: User Database (File-Based Authentication)

For homelabs with a handful of users, file-based authentication is the simplest option. You define users in a YAML file with hashed passwords.

First, generate a password hash. Authelia uses argon2id by default:

# Using Docker to run the Authelia hash utility
docker run --rm authelia/authelia:latest \
  authelia crypto hash generate argon2 \
  --password "your_secure_password_here"

This outputs a hash like:

$argon2id$v=19$m=65536,t=3,p=4$xxx...

Create the users database file:

# /opt/authelia/config/users_database.yml
users:
  jerry:
    disabled: false
    displayname: "Jerry"
    password: "$argon2id$v=19$m=65536,t=3,p=4$YOUR_HASH_HERE"
    email: jerry@yourdomain.com
    groups:
      - admins
      - dev

  sarah:
    disabled: false
    displayname: "Sarah"
    password: "$argon2id$v=19$m=65536,t=3,p=4$ANOTHER_HASH_HERE"
    email: sarah@yourdomain.com
    groups:
      - users

Groups are used in access control policies to grant different access levels to different users. An admins group might have access to infrastructure dashboards, while a users group might only access media and file services.

Step 3: Authelia Configuration

This is the main configuration file. It defines how Authelia handles sessions, storage, authentication backends, 2FA, and access control.

# /opt/authelia/config/configuration.yml

# -------------------------------------------------------------------
# Server settings
# -------------------------------------------------------------------
server:
  address: 'tcp://0.0.0.0:9091'

# -------------------------------------------------------------------
# Logging
# -------------------------------------------------------------------
log:
  level: info
  # Set to 'debug' when troubleshooting, 'info' for production

# -------------------------------------------------------------------
# JSON Web Token secret (used for identity verification links)
# -------------------------------------------------------------------
jwt_secret: "CHANGE_ME_TO_A_RANDOM_64_CHAR_STRING"

# -------------------------------------------------------------------
# Default redirection URL (where to send users after login if no
# target URL is specified)
# -------------------------------------------------------------------
default_redirection_url: "https://yourdomain.com"

# -------------------------------------------------------------------
# TOTP (Time-based One-Time Password) settings
# -------------------------------------------------------------------
totp:
  disable: false
  issuer: yourdomain.com
  algorithm: sha1
  digits: 6
  period: 30
  skew: 1
  secret_size: 32

# -------------------------------------------------------------------
# WebAuthn settings (passkeys, YubiKeys)
# -------------------------------------------------------------------
webauthn:
  disable: false
  display_name: "Authelia"
  attestation_conveyance_preference: indirect
  user_verification: preferred
  timeout: 60s

# -------------------------------------------------------------------
# Authentication backend (file-based)
# -------------------------------------------------------------------
authentication_backend:
  file:
    path: /config/users_database.yml
    password:
      algorithm: argon2id
      iterations: 3
      memory: 65536
      parallelism: 4
      key_length: 32
      salt_length: 16

# -------------------------------------------------------------------
# Session management
# -------------------------------------------------------------------
session:
  name: authelia_session
  secret: "CHANGE_ME_TO_ANOTHER_RANDOM_STRING"
  expiration: 12h
  inactivity: 45m
  remember_me: 1M
  cookies:
    - domain: yourdomain.com
      authelia_url: "https://auth.yourdomain.com"

# -------------------------------------------------------------------
# Session storage: Redis
# -------------------------------------------------------------------
  redis:
    host: authelia-redis
    port: 6379
    password: "REDIS_PASSWORD_HERE"

# -------------------------------------------------------------------
# Storage backend (SQLite for single-instance deployments)
# -------------------------------------------------------------------
storage:
  encryption_key: "CHANGE_ME_ANOTHER_RANDOM_64_CHAR_STRING"
  local:
    path: /data/db.sqlite3

# -------------------------------------------------------------------
# Notification provider (SMTP for sending emails)
# -------------------------------------------------------------------
notifier:
  smtp:
    address: "smtp://smtp.gmail.com:587"
    username: "your_email@gmail.com"
    password: "your_app_password"
    sender: "Authelia <noreply@yourdomain.com>"
    subject: "[Authelia] {title}"

# For testing without SMTP, use the filesystem notifier:
# notifier:
#   filesystem:
#     filename: /data/notification.txt

# -------------------------------------------------------------------
# Access control rules
# -------------------------------------------------------------------
access_control:
  default_policy: deny

  rules:
    # Public access to the API endpoints (needed for some integrations)
    - domain: "auth.yourdomain.com"
      policy: bypass

    # Admin services: require 2FA for admins group
    - domain:
        - "traefik.yourdomain.com"
        - "portainer.yourdomain.com"
        - "grafana.yourdomain.com"
        - "prometheus.yourdomain.com"
      policy: two_factor
      subject:
        - "group:admins"

    # Standard services: require 2FA for all users
    - domain:
        - "nextcloud.yourdomain.com"
        - "gitea.yourdomain.com"
        - "vaultwarden.yourdomain.com"
      policy: two_factor

    # Media services: require only single factor (password)
    - domain:
        - "jellyfin.yourdomain.com"
        - "immich.yourdomain.com"
      policy: one_factor

    # Catch-all: deny everything else
    # (this is redundant with default_policy: deny, but explicit is good)

A few critical items to configure:

  1. Replace all CHANGE_ME values with strong random strings. Generate them with openssl rand -hex 32.
  2. Replace yourdomain.com with your actual domain throughout the file.
  3. Configure SMTP with your actual email provider credentials.

Understanding Access Control Policies

Authelia has four policy levels:

  • bypass: No authentication required. Use for public-facing services.
  • one_factor: Username and password only. Suitable for low-sensitivity services.
  • two_factor: Username, password, and a second factor (TOTP, WebAuthn, or Duo). Use for anything sensitive.
  • deny: Block access completely. The default policy should always be deny.

Policies are evaluated from top to bottom. The first matching rule wins. Place more specific rules before general ones.

Step 4: Docker Compose Stack

Here is the complete Docker Compose file for the Authelia stack:

# /opt/authelia/docker-compose.yml
services:
  authelia:
    image: authelia/authelia:latest
    container_name: authelia
    restart: unless-stopped
    volumes:
      - ./config:/config:ro
      - ./data:/data
    environment:
      TZ: "America/New_York"
    expose:
      - 9091
    networks:
      - proxy
      - authelia-internal
    depends_on:
      authelia-redis:
        condition: service_healthy
    labels:
      # Traefik labels (see Step 5 for full Traefik config)
      - "traefik.enable=true"
      - "traefik.http.routers.authelia.rule=Host(`auth.yourdomain.com`)"
      - "traefik.http.routers.authelia.entrypoints=websecure"
      - "traefik.http.routers.authelia.tls=true"
      - "traefik.http.routers.authelia.tls.certresolver=letsencrypt"
      - "traefik.http.services.authelia.loadbalancer.server.port=9091"

  authelia-redis:
    image: redis:7-alpine
    container_name: authelia-redis
    restart: unless-stopped
    command: redis-server --requirepass REDIS_PASSWORD_HERE --maxmemory 128mb --maxmemory-policy allkeys-lru
    volumes:
      - redis_data:/data
    networks:
      - authelia-internal
    healthcheck:
      test: ["CMD", "redis-cli", "-a", "REDIS_PASSWORD_HERE", "ping"]
      interval: 10s
      timeout: 5s
      retries: 3
    expose:
      - 6379

networks:
  proxy:
    external: true
  authelia-internal:
    driver: bridge

volumes:
  redis_data:

Generate your random secrets and start the stack:

cd /opt/authelia

# Generate random secrets
JWT_SECRET=$(openssl rand -hex 32)
SESSION_SECRET=$(openssl rand -hex 32)
ENCRYPTION_KEY=$(openssl rand -hex 32)
REDIS_PASSWORD=$(openssl rand -hex 16)

echo "JWT Secret:        $JWT_SECRET"
echo "Session Secret:    $SESSION_SECRET"
echo "Encryption Key:    $ENCRYPTION_KEY"
echo "Redis Password:    $REDIS_PASSWORD"

# Update configuration.yml and docker-compose.yml with these values, then:
docker compose up -d

# Check logs for any configuration errors
docker compose logs -f authelia

Authelia should start and be accessible at https://auth.yourdomain.com (once the reverse proxy is configured).

Step 5: Traefik Integration

Traefik is the most popular reverse proxy choice for Authelia because forward authentication is natively supported through middleware. Here is a complete Traefik configuration that works with Authelia.

Traefik Docker Compose

If you do not already have Traefik running, add it to your stack or create a separate Compose file:

# /opt/traefik/docker-compose.yml
services:
  traefik:
    image: traefik:v3.3
    container_name: traefik
    restart: unless-stopped
    command:
      - "--api.dashboard=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--providers.docker.network=proxy"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.web.http.redirections.entryPoint.to=websecure"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.letsencrypt.acme.email=your_email@yourdomain.com"
      - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
      - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - traefik_letsencrypt:/letsencrypt
    networks:
      - proxy
    labels:
      - "traefik.enable=true"
      # Dashboard
      - "traefik.http.routers.traefik.rule=Host(`traefik.yourdomain.com`)"
      - "traefik.http.routers.traefik.entrypoints=websecure"
      - "traefik.http.routers.traefik.tls.certresolver=letsencrypt"
      - "traefik.http.routers.traefik.service=api@internal"
      # Protect the dashboard with Authelia
      - "traefik.http.routers.traefik.middlewares=authelia@docker"
      # Define the Authelia forward auth middleware
      - "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"

networks:
  proxy:
    external: true

volumes:
  traefik_letsencrypt:

Create the proxy network if it does not exist:

docker network create proxy

Protecting a Service with Authelia via Traefik

To protect any Docker service with Authelia, add the middleware label to its container:

# Example: protecting Grafana with Authelia
services:
  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    restart: unless-stopped
    volumes:
      - grafana_data:/var/lib/grafana
    networks:
      - proxy
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.grafana.rule=Host(`grafana.yourdomain.com`)"
      - "traefik.http.routers.grafana.entrypoints=websecure"
      - "traefik.http.routers.grafana.tls.certresolver=letsencrypt"
      - "traefik.http.routers.grafana.middlewares=authelia@docker"
      - "traefik.http.services.grafana.loadbalancer.server.port=3000"

The key line is traefik.http.routers.grafana.middlewares=authelia@docker. This tells Traefik to check with Authelia before forwarding any request to Grafana. If the user is not authenticated, they are redirected to the Authelia login portal. After authentication, they are sent back to Grafana.

Passing User Information to Backend Services

Authelia sets response headers that Traefik forwards to the backend service:

  • Remote-User: the authenticated username
  • Remote-Groups: comma-separated list of the user’s groups
  • Remote-Name: the user’s display name
  • Remote-Email: the user’s email address

Some services (like Grafana) can use these headers for automatic user provisioning. In Grafana, enable auth proxy:

# grafana.ini
[auth.proxy]
enabled = true
header_name = Remote-User
header_property = username
auto_sign_up = true
headers = "Name:Remote-Name Email:Remote-Email"

This means users who authenticate through Authelia are automatically logged into Grafana without needing a separate Grafana account.

Step 6: Nginx Integration (Alternative)

If you use Nginx instead of Traefik, Authelia integrates through the auth_request module.

Nginx Configuration

First, add a location block for the Authelia verification endpoint in your Nginx config:

# /etc/nginx/snippets/authelia-location.conf
# This file defines the internal endpoint that auth_request uses

location /authelia {
    internal;
    set $upstream_authelia http://authelia:9091/api/authz/auth-request;

    proxy_pass $upstream_authelia;

    # Pass original request information to Authelia
    proxy_set_header X-Original-Method $request_method;
    proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
    proxy_set_header X-Forwarded-For $remote_addr;
    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 Content-Length "";
    proxy_pass_request_body off;
}

Create an auth request snippet:

# /etc/nginx/snippets/authelia-authrequest.conf
# Include this in any server block that needs Authelia protection

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;
auth_request_set $name $upstream_http_remote_name;
auth_request_set $email $upstream_http_remote_email;

# If not authenticated, redirect to Authelia login portal
error_page 401 =302 https://auth.yourdomain.com/?rd=$target_url;

# Pass user information to backend
proxy_set_header Remote-User $user;
proxy_set_header Remote-Groups $groups;
proxy_set_header Remote-Name $name;
proxy_set_header Remote-Email $email;

Now protect a service by including both snippets in a server block:

# /etc/nginx/sites-available/grafana.conf
server {
    listen 443 ssl;
    server_name grafana.yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;

    include /etc/nginx/snippets/authelia-location.conf;

    location / {
        include /etc/nginx/snippets/authelia-authrequest.conf;

        proxy_pass http://grafana:3000;
        proxy_set_header Host $host;
        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;
    }
}

The Authelia portal itself needs an unprotected server block:

# /etc/nginx/sites-available/authelia.conf
server {
    listen 443 ssl;
    server_name auth.yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;

    location / {
        proxy_pass http://authelia:9091;
        proxy_set_header Host $host;
        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;
    }
}

Step 7: Setting Up Two-Factor Authentication

After deploying Authelia and logging in for the first time, you need to register a second factor. Authelia supports three methods.

TOTP (Time-Based One-Time Password)

This is the most common 2FA method. It works with any TOTP app: Google Authenticator, Authy, Bitwarden Authenticator, or any standards-compliant app.

  1. Log in to the Authelia portal at https://auth.yourdomain.com.
  2. Click the “Methods” section or navigate to 2FA settings.
  3. Select “Time-based One-Time Password” and click “Register”.
  4. Authelia sends a confirmation link to the email address associated with your account (or writes it to the notification file if using the filesystem notifier).
  5. Click the link. A QR code appears.
  6. Scan the QR code with your authenticator app.
  7. Enter the 6-digit code displayed in the app to confirm registration.

From now on, when the access control policy requires two_factor, you will be prompted for your TOTP code after entering your password.

WebAuthn (FIDO2 / Passkeys)

WebAuthn is the more secure option. It supports hardware security keys (YubiKey, SoloKeys) and platform authenticators (fingerprint readers, Face ID, Windows Hello). In 2026, passkeys have become widely supported across browsers and operating systems.

  1. Log in to the Authelia portal.
  2. Navigate to 2FA settings and select “Security Key”.
  3. Click “Register” and follow the browser prompt to insert or activate your security key.
  4. Name the key (e.g., “YubiKey 5 NFC” or “MacBook Touch ID”).

You can register multiple WebAuthn credentials. This is recommended: register at least two security keys or a security key plus a TOTP backup so you are not locked out if you lose one.

Duo Push Notifications

If you have a Duo account (free for up to 10 users), Authelia can send push notifications to the Duo Mobile app. This is the most convenient 2FA method but requires a third-party service.

# Add to configuration.yml
duo_api:
  hostname: api-XXXXXXXX.duosecurity.com
  integration_key: YOUR_DUO_INTEGRATION_KEY
  secret_key: YOUR_DUO_SECRET_KEY

For homelab use, register both TOTP and WebAuthn:

  • Use WebAuthn (passkey/security key) as the primary method for daily use. It is faster and more secure.
  • Use TOTP as a backup method in case you lose access to your security key.

Authelia allows users to choose their preferred method at login time and will remember the preference.

Step 8: Access Control Policies

The access control section in configuration.yml is where you define who can access what. Here is a more detailed example:

access_control:
  default_policy: deny

  rules:
    # Authelia portal itself must be bypassed
    - domain: "auth.yourdomain.com"
      policy: bypass

    # Public website (no auth needed)
    - domain: "www.yourdomain.com"
      policy: bypass

    # API endpoints that need to be accessible without browser auth
    - domain: "nextcloud.yourdomain.com"
      policy: bypass
      resources:
        - "^/remote.php/dav"
        - "^/remote.php/webdav"
        - "^/.well-known/"

    # Admin infrastructure: 2FA required, admins only
    - domain:
        - "traefik.yourdomain.com"
        - "portainer.yourdomain.com"
        - "proxmox.yourdomain.com"
      policy: two_factor
      subject:
        - "group:admins"

    # Development tools: 2FA required, dev group
    - domain:
        - "gitea.yourdomain.com"
        - "drone.yourdomain.com"
        - "registry.yourdomain.com"
      policy: two_factor
      subject:
        - "group:admins"
        - "group:dev"

    # Sensitive personal services: 2FA for all authenticated users
    - domain:
        - "nextcloud.yourdomain.com"
        - "vaultwarden.yourdomain.com"
      policy: two_factor

    # Media and casual services: password only
    - domain:
        - "jellyfin.yourdomain.com"
        - "immich.yourdomain.com"
        - "audiobookshelf.yourdomain.com"
      policy: one_factor

    # Internal monitoring: restrict to local network + admin 2FA
    - domain: "grafana.yourdomain.com"
      policy: two_factor
      subject:
        - "group:admins"
      networks:
        - "192.168.1.0/24"
        - "10.0.0.0/8"

Policy Design Tips

Start restrictive, loosen as needed. Set default_policy: deny and explicitly allow each service. It is much safer to get locked out of something and add a rule than to accidentally expose something sensitive.

Use groups, not individual users. Define groups like admins, dev, users, and family in your user database, then reference groups in policies. This scales better than listing individual usernames.

Use one_factor for media services. Requiring 2FA every time someone wants to watch a movie will generate complaints from family members. Use one_factor for low-sensitivity services and two_factor for anything with personal data or infrastructure access.

Use resource rules for APIs. Some services (like Nextcloud) have API endpoints that mobile apps or desktop clients access directly. These endpoints cannot handle browser-based 2FA redirects. Use resources with regex patterns to bypass Authelia for specific paths while protecting the web UI.

LDAP Authentication (Alternative to File-Based)

For setups with more than 5 users, or if you want centralized user management across multiple services, LDAP is a better backend than file-based authentication. The most popular self-hosted LDAP server for homelabs is lldap (Light LDAP), which provides a web UI for user management.

Setting Up lldap

Add lldap to your Docker Compose:

services:
  lldap:
    image: lldap/lldap:latest
    container_name: lldap
    restart: unless-stopped
    environment:
      - LLDAP_JWT_SECRET=CHANGE_ME_RANDOM_STRING
      - LLDAP_LDAP_USER_PASS=admin_password_here
      - LLDAP_LDAP_BASE_DN=dc=yourdomain,dc=com
    ports:
      - "3890:3890"   # LDAP
      - "17170:17170" # Web UI
    volumes:
      - lldap_data:/data
    networks:
      - authelia-internal

volumes:
  lldap_data:

Authelia LDAP Configuration

Replace the authentication_backend section in your Authelia configuration:

authentication_backend:
  ldap:
    address: "ldap://lldap:3890"
    implementation: custom
    timeout: 5s
    start_tls: false
    base_dn: dc=yourdomain,dc=com
    additional_users_dn: ou=people
    users_filter: "(&({username_attribute}={input})(objectClass=person))"
    additional_groups_dn: ou=groups
    groups_filter: "(member={dn})"
    user: uid=admin,ou=people,dc=yourdomain,dc=com
    password: "admin_password_here"
    attributes:
      display_name: displayName
      username: uid
      mail: mail
      group_name: cn
      member_of: memberOf

With LDAP, you manage users through the lldap web interface instead of editing a YAML file. Create users, assign them to groups, and Authelia picks up the changes automatically. Other services that support LDAP (Gitea, Nextcloud, Grafana) can also authenticate against the same lldap instance, giving you centralized user management across your entire homelab.

OpenID Connect Integration

Authelia can also function as an OpenID Connect (OIDC) provider. This is useful for services that support OIDC natively (Grafana, Gitea, Portainer, and many others), because it provides a tighter integration than forward authentication.

Add the OIDC configuration to Authelia:

# Add to configuration.yml
identity_providers:
  oidc:
    hmac_secret: "CHANGE_ME_RANDOM_64_CHAR_STRING"
    jwks:
      - key_id: "main"
        algorithm: RS256
        use: sig
        key: |
          -----BEGIN RSA PRIVATE KEY-----
          (generate with: openssl genrsa -out private.pem 4096)
          -----END RSA PRIVATE KEY-----
    clients:
      - client_id: grafana
        client_name: Grafana
        client_secret: "$pbkdf2-sha512$310000$xxx"  # hash with authelia crypto hash generate pbkdf2
        public: false
        authorization_policy: two_factor
        redirect_uris:
          - "https://grafana.yourdomain.com/login/generic_oauth"
        scopes:
          - openid
          - profile
          - groups
          - email
        userinfo_signed_response_alg: none

      - client_id: gitea
        client_name: Gitea
        client_secret: "$pbkdf2-sha512$310000$xxx"
        public: false
        authorization_policy: two_factor
        redirect_uris:
          - "https://gitea.yourdomain.com/user/oauth2/authelia/callback"
        scopes:
          - openid
          - profile
          - groups
          - email
        userinfo_signed_response_alg: none

Generate the required OIDC secrets:

# Generate RSA key for JWT signing
openssl genrsa -out /opt/authelia/config/oidc-jwks.pem 4096

# Generate client secrets (hashed)
docker run --rm authelia/authelia:latest \
  authelia crypto hash generate pbkdf2 \
  --password "your_client_secret_here"

The advantage of OIDC over forward authentication is that the backend service is fully aware of the user’s identity and can manage permissions natively. The disadvantage is that each service requires individual OIDC configuration.

Hardening and Production Tips

Use Strong Secrets

Every secret in the Authelia configuration should be a random string of at least 32 characters. Never reuse secrets across different fields.

# Generate strong random strings
openssl rand -hex 32

Enable Rate Limiting

Authelia has built-in brute force protection, but you should also rate-limit at the reverse proxy level:

# Traefik rate limiting middleware
labels:
  - "traefik.http.middlewares.ratelimit.ratelimit.average=10"
  - "traefik.http.middlewares.ratelimit.ratelimit.burst=20"
  - "traefik.http.middlewares.ratelimit.ratelimit.period=1m"

Ban Configuration

Authelia’s regulation feature bans users after too many failed attempts:

# Add to configuration.yml
regulation:
  max_retries: 3
  find_time: 2m
  ban_time: 5m

After 3 failed attempts within 2 minutes, the user is banned for 5 minutes.

Back Up Your Data

The critical files to back up are:

  • /opt/authelia/config/configuration.yml — your entire Authelia configuration
  • /opt/authelia/config/users_database.yml — user credentials (if using file-based auth)
  • /opt/authelia/data/db.sqlite3 — TOTP secrets, WebAuthn registrations, authentication logs
  • Your OIDC private key if using OpenID Connect

Losing the SQLite database means all users will need to re-register their 2FA devices.

Use PostgreSQL for Production

SQLite works fine for a single-instance deployment, but if you want high availability or better performance under load, switch to PostgreSQL:

storage:
  encryption_key: "YOUR_ENCRYPTION_KEY"
  postgres:
    address: "tcp://postgres:5432"
    database: authelia
    username: authelia
    password: "POSTGRES_PASSWORD"
    schema: public

Troubleshooting Common Issues

”Access Denied” After Login

Symptom: You authenticate successfully but get a 403 when redirected to the target service.

Cause: Your access control rules do not match the domain or user/group combination.

Fix: Check configuration.yml access control rules. Set log.level: debug and check Authelia logs for the exact policy decision:

docker compose logs -f authelia | grep "access control"

Redirect Loop

Symptom: The browser loops between the target service and auth.yourdomain.com endlessly.

Cause: The Authelia portal itself is behind the Authelia middleware, creating a loop.

Fix: Ensure the Authelia domain has a bypass policy in access control rules, and do NOT apply the Authelia middleware to the Authelia container’s Traefik router.

TOTP Codes Not Accepted

Symptom: The 6-digit code from your authenticator app is always rejected.

Cause: Clock drift between your server and the TOTP app. TOTP is time-based and requires clocks to be synchronized within 30 seconds.

Fix: Ensure NTP is running on your server:

timedatectl status
# If NTP is not active:
sudo timedatectl set-ntp true

Session Not Persisting Across Services

Symptom: You authenticate for one service but are prompted to log in again for another.

Cause: The session cookie domain does not match. All protected services must be subdomains of the domain specified in the session cookie configuration.

Fix: Ensure session.cookies[0].domain is set to your root domain (e.g., yourdomain.com, not auth.yourdomain.com). All services must be under this domain: grafana.yourdomain.com, nextcloud.yourdomain.com, etc.

Mobile Apps Cannot Authenticate

Symptom: Nextcloud, Jellyfin, or other mobile apps cannot connect because they do not handle browser-based authentication.

Fix: Add bypass rules for API endpoints that mobile apps use:

- domain: "nextcloud.yourdomain.com"
  policy: bypass
  resources:
    - "^/remote.php/(dav|webdav)"
    - "^/ocs/"
    - "^/status.php"

- domain: "jellyfin.yourdomain.com"
  policy: bypass
  resources:
    - "^/api/"

Be aware that bypassing API endpoints means those endpoints are protected only by the application’s built-in authentication. For most self-hosted apps, this is acceptable because their APIs require authentication tokens.

FAQ

Does Authelia work with services that have their own login?

Yes. Authelia sits in front of the service and handles authentication before the request reaches the service. If the service also has its own login (like Grafana or Nextcloud), users will see both if you do not configure header-based auth passthrough. For the best experience, configure the backend service to trust the Remote-User header so users are automatically logged in after Authelia authentication.

Can I use Authelia without a domain name?

Technically yes, using local DNS (like Pi-hole or a hosts file) and self-signed certificates. But this is painful. Authelia’s session cookies require HTTPS and a consistent domain. For the best experience, register a domain, point it to your server (or use Cloudflare Tunnel), and let your reverse proxy handle TLS certificates.

How much RAM does Authelia use?

Authelia itself uses about 30-50 MB of RAM. Redis adds another 20-50 MB. Total for the Authelia stack is typically under 150 MB, making it one of the lightest SSO solutions available.

Can family members use Authelia without 2FA?

Yes. Set their services to one_factor policy, which only requires a password. Or create a “family” group and set policies per group. The recommendation is to use one_factor for media services (Jellyfin, Immich) and two_factor for anything with personal data.

What happens if Authelia goes down?

If Authelia is unreachable, the reverse proxy’s forward auth check fails, and users cannot access any protected service. This is a security feature: fail closed, not fail open. To mitigate this, ensure Authelia has restart: unless-stopped in Docker Compose, and consider running it on your most reliable node.

Can I use Authelia with Cloudflare Tunnel?

Yes, and this is a popular setup for homelabs without a static IP. Configure Cloudflare Tunnel to route traffic to your Traefik or Nginx instance, and the rest of the configuration is the same. Make sure to set X-Forwarded-For headers correctly so Authelia sees the real client IP.

How do I add a new user?

For file-based auth: generate a password hash, add the user to users_database.yml, and restart Authelia. For LDAP: add the user through the lldap web UI. No Authelia restart needed.

Is Authelia better than Authentik?

Both are excellent self-hosted SSO solutions. Authelia is lighter (50 MB RAM vs 500+ MB for Authentik) and simpler for forward-auth-only setups. Authentik is a full identity provider with a web UI for user management, application management, and policy configuration. If you just need SSO and 2FA in front of existing services, Authelia is the simpler choice. If you need a full identity platform with self-service user registration, social login, and complex RBAC, Authentik is more appropriate.

Conclusion

Authelia solves one of the most common pain points in a growing homelab: fragmented authentication across dozens of services. Instead of maintaining separate accounts and passwords for every application, you get a single login portal with enforced two-factor authentication.

The setup is not trivial — you need to configure the reverse proxy integration, define access control policies, and manage secrets carefully. But once it is running, it is remarkably low-maintenance. Users authenticate once and move freely between services. New services get protected by adding a single middleware label (Traefik) or include directive (Nginx). And the entire stack runs in under 150 MB of RAM.

Start with file-based authentication and TOTP for your second factor. As your homelab grows, migrate to LDAP with lldap for centralized user management, and add OpenID Connect for services that support it natively. Register a WebAuthn security key as your primary 2FA method and keep TOTP as a backup.

The most important thing is to actually deploy it. Every service you run without authentication is a service that anyone on your network (or the internet, if it is publicly accessible) can access. Authelia makes the authentication part straightforward enough that there is no good reason to skip it.