Skip to Content

Self-Host Authelia: Add Two-Factor Authentication to Any Service in 15 Minutes

Add SSO, 2FA, and access control to every self-hosted service with Authelia

Want to add a security layer in front of your self-hosted apps without modifying each one? Authelia acts as an authentication gateway that sits between your reverse proxy and your applications, adding SSO, 2FA, and access control to anything you run.

Why Authelia?

Every self-hosted service has its own login system. Jellyfin has one. Grafana has another. Your download client? Another password to remember. Authelia solves this by becoming the single authentication point for your entire homelab.

Here's what you get:

  • Single Sign-On (SSO): Log in once, access everything
  • Two-Factor Authentication (2FA): TOTP, WebAuthn, or Duo Security
  • Access Control: Different rules for different users, IPs, or times
  • Per-Service Policies: Require 2FA for admin panels, allow LAN access to media servers
  • Lightweight: Single Go binary, minimal resource usage

How It Works

Authelia sits behind your reverse proxy (Nginx, Traefik, Caddy). When someone requests a protected service, the proxy checks with Authelia first. If not authenticated, they're redirected to Authelia's login portal. After successful auth (with optional 2FA), Authelia tells the proxy to let them through.

Internet → Reverse Proxy → Authelia (auth check) → Your App
                    ↓
              Not authenticated? → Authelia login portal

Prerequisites

  • A VPS or server with Docker installed
  • A reverse proxy already configured (Traefik, Nginx, or Caddy)
  • A domain name with DNS pointing to your server
  • SMTP access for email notifications (optional but recommended)

Minimum specs: 1 CPU, 512MB RAM, 1GB disk. Authelia is extremely lightweight.

Step 1: Create the Directory Structure

mkdir -p ~/authelia/config
cd ~/authelia

Step 2: Create the Docker Compose File

version: "3.8"

services:
  authelia:
    image: authelia/authelia:latest
    container_name: authelia
    restart: unless-stopped
    ports:
      - "9091:9091"
    volumes:
      - ./config:/config
    environment:
      - TZ=America/New_York
      - AUTHELIA_JWT_SECRET_FILE=/config/secrets/jwt
      - AUTHELIA_SESSION_SECRET_FILE=/config/secrets/session
      - AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE=/config/secrets/storage
    healthcheck:
      test: ["CMD", "authelia", "healthcheck"]
      interval: 30s
      timeout: 3s
      retries: 3

Step 3: Generate Secrets

mkdir -p config/secrets
openssl rand -hex 32 | tr -d '
' > config/secrets/jwt
openssl rand -hex 32 | tr -d '
' > config/secrets/session
openssl rand -hex 32 | tr -d '
' > config/secrets/storage

Step 4: Create the Configuration

Create config/configuration.yml:

---
server:
  host: 0.0.0.0
  port: 9091

log:
  level: info

theme: dark

jwt_secret: ""  # From file

default_redirection_url: https://auth.yourdomain.com

totp:
  issuer: authelia.com
  period: 30
  skew: 1

webauthn:
  disable: false
  display_name: Authelia
  attestation_conveyance: none
  user_verification: preferred
  timeout: 60s

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:
  default_policy: deny
  rules:
    # Bypass auth for health checks
    - domain: "auth.yourdomain.com"
      policy: bypass
    
    # Require 2FA for admin services
    - domain: "portainer.yourdomain.com"
      policy: two_factor
    - domain: "grafana.yourdomain.com"
      policy: two_factor
    
    # One factor for media (convenience on LAN)
    - domain: "jellyfin.yourdomain.com"
      networks:
        - 192.168.1.0/24
      policy: one_factor
    - domain: "jellyfin.yourdomain.com"
      policy: two_factor
    
    # Everything else requires at least password
    - domain: "*.yourdomain.com"
      policy: one_factor

session:
  name: authelia_session
  secret: ""  # From file
  expiration: 1h
  inactivity: 5m
  remember_me_duration: 1M
  cookies:
    - domain: yourdomain.com
      authelia_url: https://auth.yourdomain.com
      default_redirection_url: https://yourdomain.com

storage:
  encryption_key: ""  # From file
  local:
    path: /config/db.sqlite3

notifier:
  smtp:
    host: smtp.gmail.com
    port: 587
    username: [email protected]
    password: your-app-password
    sender: Authelia 

Step 5: Create User Database

Create config/users_database.yml:

---
users:
  ali:
    displayname: "Ali"
    password: "$argon2id$v=19$m=65536,t=3,p=4$..."  # Generate below
    email: [email protected]
    groups:
      - admins
      - users

Generate a password hash:

docker run authelia/authelia:latest authelia crypto hash generate argon2 --password 'YourSecurePassword'

Copy the output into the password field.

Step 6: Start Authelia

docker compose up -d
docker logs -f authelia

Wait for: Startup complete. Access the portal at https://auth.yourdomain.com (or http://your-server-ip:9091 if testing without a reverse proxy).

Step 7: Configure Your Reverse Proxy

Traefik

# docker-compose.yml labels for protected service
labels:
  - "traefik.enable=true"
  - "traefik.http.routers.app.rule=Host(`app.yourdomain.com`)"
  - "traefik.http.routers.app.middlewares=authelia@docker"
  - "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"

Nginx

location / {
    proxy_pass http://app:3000;
    
    # Authelia auth request
    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;
    proxy_set_header Remote-User $user;
    proxy_set_header Remote-Groups $groups;
    proxy_set_header Remote-Name $name;
    proxy_set_header Remote-Email $email;
}

location /authelia {
    internal;
    proxy_pass http://authelia:9091/api/authz/auth-request;
    proxy_pass_request_body off;
    proxy_set_header Content-Length "";
    proxy_set_header X-Original-URL $target_url;
}

Verification

  1. Visit a protected service (e.g., https://portainer.yourdomain.com)
  2. You should be redirected to Authelia's login page
  3. Log in with your username/password
  4. If 2FA is required, you'll be prompted to register a TOTP app or WebAuthn key
  5. After authentication, you're redirected to the original service
  6. Check that the service sees your username in headers (most apps auto-login or pre-fill)

Testing Access Rules

# Test from LAN (should allow 1FA for Jellyfin)
curl -H "Host: jellyfin.yourdomain.com" http://localhost/

# Test from "outside" (simulate with X-Forwarded-For)
curl -H "Host: jellyfin.yourdomain.com"      -H "X-Forwarded-For: 8.8.8.8"      http://localhost/

Next Steps

  • Add more services: Just add domain rules in access_control
  • Enable WebAuthn: Use hardware keys (YubiKey) for phishing-resistant 2FA
  • LDAP integration: Connect to existing user directories
  • File-based secrets: Move secrets to Docker secrets or Vault
  • High availability: Run multiple instances with Redis session storage

Related Guides

Authelia gives you enterprise-grade authentication without the enterprise complexity. Your homelab just got a serious security upgrade.

Self-Host Redis: High-Performance In-Memory Caching in 10 Minutes