If you’re running multiple self-hosted services, you’ve probably noticed the login fatigue. Nextcloud has its own login. Portainer has another. Grafana, Gitea, Jellyfin — each with separate credentials. It’s a mess.

Authelia solves this by acting as a single sign-on (SSO) gateway with built-in two-factor authentication (2FA). Put it in front of your reverse proxy and every service gets enterprise-grade authentication without touching the service itself.

What is Authelia?

Authelia is an open-source authentication and authorization server. It works as a companion to your reverse proxy (Nginx Proxy Manager, Traefik, Caddy) and provides:

  • Single Sign-On (SSO) — Log in once, access everything
  • Two-Factor Authentication — TOTP, WebAuthn/FIDO2, Duo Push
  • Access Control — Per-service, per-user, per-group policies
  • Brute Force Protection — Rate limiting and account lockout
  • Session Management — Configurable timeouts and cookie domains

Prerequisites

Before starting, you’ll need:

  • A Linux server with Docker and Docker Compose installed
  • A reverse proxy (this guide uses Nginx Proxy Manager)
  • A domain name pointing to your server
  • Basic familiarity with YAML configuration

Step 1: Create the Directory Structure

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

Step 2: Generate Secrets

Authelia needs several secrets. Generate them securely:

# JWT secret
openssl rand -hex 32 > config/jwt_secret

# Session secret
openssl rand -hex 32 > config/session_secret

# Storage encryption key
openssl rand -hex 32 > config/storage_encryption_key

Step 3: Create the Docker Compose File

Create docker-compose.yml:

version: "3.8"

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

  redis:
    image: redis:7-alpine
    container_name: authelia-redis
    restart: unless-stopped
    volumes:
      - redis_data:/data
    expose:
      - 6379

volumes:
  redis_data:

Step 4: Configure Authelia

Create config/configuration.yml:

# Authelia Configuration
theme: dark

server:
  address: "tcp://0.0.0.0:9091"

log:
  level: info

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

authentication_backend:
  file:
    path: /config/users_database.yml
    password:
      algorithm: argon2id
      iterations: 3
      memory: 65536
      parallelism: 4
      salt_length: 16

access_control:
  default_policy: deny
  rules:
    # Public access to the auth portal itself
    - domain: auth.yourdomain.com
      policy: bypass

    # Services that need 2FA
    - domain: "*.yourdomain.com"
      policy: two_factor

session:
  name: authelia_session
  secret: # contents of config/session_secret
  expiration: 3600
  inactivity: 300
  domain: yourdomain.com

  redis:
    host: authelia-redis
    port: 6379

regulation:
  max_retries: 3
  find_time: 120
  ban_time: 300

storage:
  encryption_key: # contents of config/storage_encryption_key
  local:
    path: /config/db.sqlite3

notifier:
  filesystem:
    filename: /config/notification.txt

Production tip: Replace the filesystem notifier with SMTP for real email notifications. The filesystem notifier writes verification links to a file, which is fine for testing.

Step 5: Create a User Database

Create config/users_database.yml:

users:
  admin:
    displayname: "Admin User"
    # Generate with: authelia crypto hash generate argon2
    password: "$argon2id$v=19$m=65536,t=3,p=4$YOUR_HASH_HERE"
    email: [email protected]
    groups:
      - admins
      - users

Generate the password hash:

docker run --rm authelia/authelia:latest \
  authelia crypto hash generate argon2 \
  --password 'YourSecurePassword123!'

Copy the output hash into the password field.

Step 6: Start Authelia

docker compose up -d

Check the logs:

docker compose logs -f authelia

You should see Authelia start on port 9091 without errors.

Step 7: Configure Your Reverse Proxy

Nginx Proxy Manager

For each service you want to protect, add these to the Advanced tab of the proxy host:

location /authelia {
    internal;
    set $upstream_authelia http://authelia:9091/api/verify;
    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_set_header Connection "";
}

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;

error_page 401 =302 https://auth.yourdomain.com/?rd=$target_url;

Traefik (Alternative)

If you use Traefik, add this middleware:

# In your Traefik dynamic config
http:
  middlewares:
    authelia:
      forwardAuth:
        address: "http://authelia:9091/api/verify?rd=https://auth.yourdomain.com"
        trustForwardHeader: true
        authResponseHeaders:
          - "Remote-User"
          - "Remote-Groups"
          - "Remote-Name"
          - "Remote-Email"

Then add authelia middleware to any service’s router.

Step 8: Set Up Two-Factor Authentication

  1. Navigate to https://auth.yourdomain.com
  2. Log in with your credentials
  3. You’ll be prompted to register a 2FA device
  4. Scan the QR code with your authenticator app (Aegis, Authy, Google Authenticator)
  5. Enter the code to verify

Access Control Policies

Authelia supports granular access control. Here are common patterns:

access_control:
  default_policy: deny
  rules:
    # Public services (no auth needed)
    - domain: public.yourdomain.com
      policy: bypass

    # Services needing just a password
    - domain: jellyfin.yourdomain.com
      policy: one_factor

    # Admin services needing 2FA
    - domain:
        - portainer.yourdomain.com
        - proxmox.yourdomain.com
      policy: two_factor
      subject:
        - "group:admins"

    # Everything else requires 2FA
    - domain: "*.yourdomain.com"
      policy: two_factor

Troubleshooting

“Access Denied” on all services

Check that your reverse proxy can reach Authelia on port 9091. They need to be on the same Docker network.

docker network ls
docker network inspect <network_name>

If using the filesystem notifier, check the notification file:

cat ~/authelia/config/notification.txt

The registration link will be there. For production, configure SMTP.

Session not persisting across subdomains

Make sure the session.domain in your config matches your root domain (not a subdomain):

session:
  domain: yourdomain.com  # NOT auth.yourdomain.com

Redis connection errors

Ensure the Redis container is running and on the same Docker network:

docker compose ps
docker compose logs redis

Security Hardening

Once basic setup works, consider these improvements:

  1. Enable SMTP notifications — Replace filesystem notifier with real email
  2. Add WebAuthn — Hardware security keys (YubiKey) for strongest 2FA
  3. Set up LDAP — If you have many users, use LDAP instead of the file backend
  4. Configure IP-based rules — Allow bypass from your LAN, require 2FA from WAN
  5. Enable password policy — Enforce minimum length and complexity

What’s Next?

With Authelia protecting your services, you now have:

  • ✅ One login for all your self-hosted apps
  • ✅ Two-factor authentication everywhere
  • ✅ Brute force protection
  • ✅ Granular access control per service

Consider pairing Authelia with WireGuard VPN for the ultimate security setup — VPN for network access, Authelia for application authentication.


Authelia is free, open-source, and actively maintained. For more configuration options, check the official documentation.