← All articles
SECURITY Authelia: Single Sign-On and 2FA for Your Home Lab 2026-02-09 · authelia · sso · authentication

Authelia: Single Sign-On and 2FA for Your Home Lab

Security 2026-02-09 authelia sso authentication security

Most self-hosted services have their own login page. Nextcloud has one. Gitea has one. Grafana has one. Portainer has one. Each with its own password, its own session management, its own 2FA setup. Before long, you're managing a dozen credentials and typing passwords constantly.

Authelia is an authentication and authorization server that sits in front of your reverse proxy and provides a single login for all your services. Log in once, get access to everything your policy allows. It supports TOTP (authenticator apps), WebAuthn (hardware keys like YubiKey), and push notifications for two-factor authentication. It's lightweight, runs as a single binary or container, and is purpose-built for the homelab use case.

How Authelia Works

Authelia integrates with your reverse proxy (Nginx, Traefik, Caddy, HAProxy) using a "forward auth" pattern:

  1. A user requests nextcloud.yourdomain.com
  2. Your reverse proxy intercepts the request and asks Authelia: "Is this user authenticated?"
  3. If no: Authelia redirects to its login portal at auth.yourdomain.com
  4. The user logs in (username + password + optional 2FA)
  5. Authelia sets a session cookie and redirects back to the original URL
  6. The reverse proxy sees the valid session and forwards the request to Nextcloud

From the user's perspective: you visit a service, get redirected to a login page, authenticate once, and then every other service behind Authelia recognizes your session. That's SSO.

Installation with Docker

Authelia is a single container with a YAML configuration file.

Directory Structure

mkdir -p ~/docker/authelia/{config,secrets}

Docker Compose

# ~/docker/authelia/docker-compose.yml
services:
  authelia:
    image: authelia/authelia:latest
    container_name: authelia
    restart: unless-stopped
    ports:
      - "9091:9091"
    volumes:
      - ./config:/config
    environment:
      TZ: "America/New_York"
      AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET_FILE: /config/secrets/jwt
      AUTHELIA_SESSION_SECRET_FILE: /config/secrets/session
      AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE: /config/secrets/storage

Generate Secrets

# Generate random secrets
openssl rand -hex 32 > ~/docker/authelia/config/secrets/jwt
openssl rand -hex 32 > ~/docker/authelia/config/secrets/session
openssl rand -hex 32 > ~/docker/authelia/config/secrets/storage

Main Configuration

Create ~/docker/authelia/config/configuration.yml:

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

# Logging
log:
  level: info

# The domain for Authelia's portal
default_2fa_method: totp

# Theme
theme: dark

# TOTP settings
totp:
  issuer: homelab
  period: 30
  digits: 6

# WebAuthn settings
webauthn:
  disable: false
  display_name: Homelab
  attestation_conveyance_preference: indirect
  user_verification: preferred
  timeout: 60s

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

# Access control policies
access_control:
  default_policy: deny

  rules:
    # Public access — no authentication needed
    - domain: "public.yourdomain.com"
      policy: bypass

    # Single factor — username and password only
    - domain: "*.yourdomain.com"
      policy: one_factor
      subject:
        - "group:admins"

    # Two factor for sensitive services
    - domain:
        - "proxmox.yourdomain.com"
        - "portainer.yourdomain.com"
        - "vault.yourdomain.com"
      policy: two_factor

# Session configuration
session:
  name: authelia_session
  cookies:
    - domain: 'yourdomain.com'
      authelia_url: 'https://auth.yourdomain.com'
      default_redirection_url: 'https://home.yourdomain.com'
  expiration: 12h
  inactivity: 45m

# Storage backend — SQLite for simplicity
storage:
  local:
    path: /config/db.sqlite3

# Notification delivery — for password resets and 2FA registration
notifier:
  # For testing: writes notifications to a file
  filesystem:
    filename: /config/notification.txt

  # For production: use SMTP
  # smtp:
  #   address: 'smtp://smtp.example.com:587'
  #   sender: 'authelia@yourdomain.com'
  #   username: 'your-smtp-user'
  #   password: 'your-smtp-password'

Create Users

Create ~/docker/authelia/config/users_database.yml:

users:
  admin:
    disabled: false
    displayname: "Admin"
    email: admin@yourdomain.com
    groups:
      - admins
      - dev
    password: "$argon2id$v=19$m=65536,t=3,p=4$..."  # Generated below

Generate a password hash:

docker run --rm authelia/authelia:latest \
  authelia crypto hash generate argon2 \
  --password 'your-secure-password'

Copy the output hash into the password field.

Start Authelia

cd ~/docker/authelia && docker compose up -d

# Check logs
docker logs authelia

Visit http://your-server-ip:9091 to verify Authelia is running. You should see the login portal.

Reverse Proxy Integration

The key to making Authelia work is configuring your reverse proxy to check authentication on every request. Here's how to set it up with the three most popular homelab reverse proxies.

Traefik

Traefik has excellent Authelia support through its ForwardAuth middleware.

Add to your Traefik dynamic configuration or docker-compose labels:

# Traefik dynamic config (file provider)
http:
  middlewares:
    authelia:
      forwardAuth:
        address: "http://authelia:9091/api/authz/forward-auth"
        trustForwardHeader: true
        authResponseHeaders:
          - "Remote-User"
          - "Remote-Groups"
          - "Remote-Email"
          - "Remote-Name"

Then apply the middleware to any service that should be protected:

# In your service's docker-compose.yml
services:
  grafana:
    image: grafana/grafana:latest
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.grafana.rule=Host(`grafana.yourdomain.com`)"
      - "traefik.http.routers.grafana.tls.certresolver=letsencrypt"
      - "traefik.http.routers.grafana.middlewares=authelia@file"

Don't forget the Authelia service itself needs a Traefik route WITHOUT the auth middleware (otherwise you can't reach the login page):

services:
  authelia:
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.authelia.rule=Host(`auth.yourdomain.com`)"
      - "traefik.http.routers.authelia.tls.certresolver=letsencrypt"
      # No authelia middleware here — this IS the auth service

Nginx / Nginx Proxy Manager

For Nginx, you use the auth_request module:

# /etc/nginx/snippets/authelia.conf
# Include this in any server block that should be protected

location /authelia {
    internal;
    set $upstream_authelia http://authelia:9091/api/authz/auth-request;
    proxy_pass $upstream_authelia;
    proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
    proxy_set_header X-Forwarded-Method $request_method;
    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-For $remote_addr;
    proxy_set_header Content-Length "";
    proxy_pass_request_body off;
}
# In your service's server block
server {
    server_name grafana.yourdomain.com;

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

    location / {
        auth_request /authelia;
        auth_request_set $user $upstream_http_remote_user;
        auth_request_set $groups $upstream_http_remote_groups;
        proxy_set_header Remote-User $user;
        proxy_set_header Remote-Groups $groups;
        proxy_pass http://grafana:3000;
    }
}

Caddy

Caddy's integration is clean thanks to the forward_auth directive:

# Caddyfile
auth.yourdomain.com {
    reverse_proxy authelia:9091
}

grafana.yourdomain.com {
    forward_auth authelia:9091 {
        uri /api/authz/forward-auth
        copy_headers Remote-User Remote-Groups Remote-Email Remote-Name
    }
    reverse_proxy grafana:3000
}

nextcloud.yourdomain.com {
    forward_auth authelia:9091 {
        uri /api/authz/forward-auth
        copy_headers Remote-User Remote-Groups Remote-Email Remote-Name
    }
    reverse_proxy nextcloud:80
}

Access Control Policies

Authelia's access control is one of its strongest features. You can define fine-grained rules based on domain, user, group, network, and method.

Policy Levels

Example Policies

access_control:
  default_policy: deny

  rules:
    # Public-facing services
    - domain: "blog.yourdomain.com"
      policy: bypass

    - domain: "status.yourdomain.com"
      policy: bypass

    # General services — password login is sufficient
    - domain:
        - "jellyfin.yourdomain.com"
        - "nextcloud.yourdomain.com"
        - "gitea.yourdomain.com"
      policy: one_factor

    # Admin services — require 2FA
    - domain:
        - "proxmox.yourdomain.com"
        - "portainer.yourdomain.com"
        - "vault.yourdomain.com"
        - "router.yourdomain.com"
      policy: two_factor

    # API endpoints — bypass for webhooks
    - domain: "gitea.yourdomain.com"
      resources:
        - "^/api/.*"
      policy: bypass

    # Restrict admin panels to specific users
    - domain: "*.yourdomain.com"
      resources:
        - "^/admin.*"
      policy: two_factor
      subject:
        - "group:admins"

    # Allow local network without authentication
    - domain: "*.yourdomain.com"
      policy: bypass
      networks:
        - "192.168.1.0/24"

The last rule is particularly useful for homelabs: when you're on your home network, skip authentication entirely. Only require login when accessing from outside.

Setting Up 2FA

TOTP (Authenticator Apps)

After logging in with username and password, Authelia prompts you to register a TOTP device if 2FA is required.

  1. Log in to the Authelia portal
  2. Navigate to the 2FA registration page
  3. Scan the QR code with your authenticator app (Google Authenticator, Authy, Bitwarden)
  4. Enter the verification code

If you configured the filesystem notifier, the registration link is written to /config/notification.txt. For production, use SMTP so the link is emailed to you.

WebAuthn (Hardware Keys)

WebAuthn lets you use hardware security keys (YubiKey, Google Titan) or platform authenticators (fingerprint, Face ID) for 2FA.

Enable in configuration (it's on by default):

webauthn:
  disable: false
  display_name: Homelab

Register your key through the Authelia portal. After registration, you can tap your hardware key instead of entering a TOTP code.

WebAuthn is strictly more secure than TOTP — it's phishing-resistant and doesn't require typing codes. If you have a YubiKey, use it.

OIDC Provider

Authelia can act as an OpenID Connect (OIDC) identity provider. This means services that support OIDC login (Gitea, Grafana, Nextcloud, Portainer, many others) can authenticate directly through Authelia, providing true SSO where you click "Login with Authelia" and you're in.

Configure OIDC

Add to configuration.yml:

identity_providers:
  oidc:
    hmac_secret: 'generate-a-long-random-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: 'gitea'
        client_name: 'Gitea'
        client_secret: '$pbkdf2-sha512$...'  # Hash of the secret
        public: false
        authorization_policy: 'one_factor'
        redirect_uris:
          - 'https://gitea.yourdomain.com/user/oauth2/authelia/callback'
        scopes:
          - 'openid'
          - 'profile'
          - 'groups'
          - 'email'

      - client_id: 'grafana'
        client_name: 'Grafana'
        client_secret: '$pbkdf2-sha512$...'
        public: false
        authorization_policy: 'one_factor'
        redirect_uris:
          - 'https://grafana.yourdomain.com/login/generic_oauth'
        scopes:
          - 'openid'
          - 'profile'
          - 'groups'
          - 'email'

Generate client secrets:

docker run --rm authelia/authelia:latest \
  authelia crypto hash generate pbkdf2 \
  --password 'your-client-secret'

Configure Gitea for OIDC

In Gitea's admin panel, add an OAuth2 authentication source:

Now Gitea shows a "Sign in with Authelia" button on its login page.

Configure Grafana for OIDC

Add to Grafana's environment variables:

environment:
  GF_AUTH_GENERIC_OAUTH_ENABLED: "true"
  GF_AUTH_GENERIC_OAUTH_NAME: "Authelia"
  GF_AUTH_GENERIC_OAUTH_CLIENT_ID: "grafana"
  GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET: "your-client-secret"
  GF_AUTH_GENERIC_OAUTH_SCOPES: "openid profile groups email"
  GF_AUTH_GENERIC_OAUTH_AUTH_URL: "https://auth.yourdomain.com/api/oidc/authorization"
  GF_AUTH_GENERIC_OAUTH_TOKEN_URL: "https://auth.yourdomain.com/api/oidc/token"
  GF_AUTH_GENERIC_OAUTH_API_URL: "https://auth.yourdomain.com/api/oidc/userinfo"
  GF_AUTH_GENERIC_OAUTH_LOGIN_ATTRIBUTE_PATH: "preferred_username"
  GF_AUTH_GENERIC_OAUTH_GROUPS_ATTRIBUTE_PATH: "groups"
  GF_AUTH_GENERIC_OAUTH_NAME_ATTRIBUTE_PATH: "name"
  GF_AUTH_GENERIC_OAUTH_ROLE_ATTRIBUTE_PATH: "contains(groups[*], 'admins') && 'Admin' || 'Viewer'"

LDAP Backend

For homelabs with more than a couple of users, LDAP provides a centralized user directory. Authelia can authenticate against an LDAP server like lldap (a lightweight LDAP designed for homelabs) or OpenLDAP.

Using lldap

lldap is a purpose-built, lightweight LDAP server for authentication. It has a web UI for user management and uses a fraction of the resources of OpenLDAP.

# docker-compose.yml for lldap
services:
  lldap:
    image: lldap/lldap:latest
    container_name: lldap
    restart: unless-stopped
    ports:
      - "3890:3890"   # LDAP
      - "17170:17170" # Web UI
    environment:
      LLDAP_JWT_SECRET: "generate-a-random-string"
      LLDAP_LDAP_USER_PASS: "admin-password"
      LLDAP_LDAP_BASE_DN: "dc=yourdomain,dc=com"
    volumes:
      - ./lldap_data:/data

Configure Authelia to use LDAP:

authentication_backend:
  ldap:
    address: 'ldap://lldap:3890'
    implementation: 'lldap'
    base_dn: 'dc=yourdomain,dc=com'
    users_filter: '(&({username_attribute}={input})(objectClass=person))'
    groups_filter: '(member={dn})'
    user: 'uid=admin,ou=people,dc=yourdomain,dc=com'
    password: 'admin-password'
    attributes:
      username: 'uid'
      display_name: 'cn'
      mail: 'mail'
      group_name: 'cn'

Now you manage users in lldap's web UI, and Authelia authenticates against it. Any service that supports LDAP can also authenticate directly against lldap, giving you a true centralized user directory.

Authelia vs Authentik vs Keycloak

These are the three main self-hosted SSO/authentication options. Here's an honest comparison:

Feature Authelia Authentik Keycloak
Resource usage ~50MB RAM ~1GB RAM ~1.5GB RAM
Setup complexity Simple YAML config Web-based setup wizard Complex, many options
OIDC/OAuth2 provider Yes Yes Yes
SAML provider No Yes Yes
LDAP backend Yes Built-in directory Yes
Web UI for user management No (file/LDAP) Yes Yes
Forward auth (proxy integration) Primary use case Supported Supported
Social login (Google, GitHub) Via OIDC proxy Built-in Built-in
Flows/customization Limited Visual flow editor Extensive
Documentation Good Good Extensive
Target audience Homelabbers Homelabbers/small orgs Enterprise

Choose Authelia when: You want something lightweight, simple to configure, and focused on protecting services behind a reverse proxy. You have a small number of users (family, maybe a few friends) and don't need social login or SAML.

Choose Authentik when: You want a more feature-rich solution with a web UI for user management, built-in user enrollment flows, and social login support. You're willing to spend more RAM for more features.

Choose Keycloak when: You have enterprise requirements — SAML support, complex authentication flows, federation with corporate identity providers, or compliance needs. Keycloak is powerful but complex. For most homelabs, it's overkill.

The homelab sweet spot: Authelia with lldap. You get lightweight SSO with 2FA, a web UI for user management (via lldap), OIDC for services that support it, and forward auth for everything else. Total RAM usage: under 200MB.

Troubleshooting

Redirect loops after login: Your reverse proxy isn't correctly forwarding the authentication headers. Double-check the authResponseHeaders in Traefik or the auth_request_set directives in Nginx.

"Access denied" after successful login: Check your access control rules. The default_policy: deny blocks everything not explicitly allowed. Add a rule for the domain you're trying to access.

2FA registration link not received: If using the filesystem notifier, check /config/notification.txt. For SMTP, check Authelia logs for email delivery errors.

Session not persisting across subdomains: Make sure the session domain is set to your root domain (e.g., yourdomain.com, not auth.yourdomain.com). The session cookie must be valid for all subdomains.

# Check Authelia logs for detailed errors
docker logs authelia --tail 100

# Test the authentication endpoint directly
curl -I -X GET https://auth.yourdomain.com/api/authz/forward-auth \
  -H "X-Original-URL: https://grafana.yourdomain.com/"

Authelia is one of those homelab tools that, once set up, makes everything else better. You stop juggling passwords, you get 2FA on services that don't natively support it, and you can share access to services without creating separate accounts everywhere. The initial configuration takes an hour or two, but you'll save that time back within the first month.