Authelia SSO with Nginx: Two-Factor Authentication for Internal Apps
Security

Authelia SSO with Nginx: Two-Factor Authentication for Internal Apps

  • AuthorKacey H.
  • Date
  • Time28 minutes

Authelia is an open-source authentication and authorization server that adds single sign-on (SSO) and two-factor authentication (2FA) in front of your internal applications. Instead of managing separate logins for Grafana, Proxmox, Portainer, and other admin panels, Authelia provides a unified login portal with TOTP, WebAuthn, or Duo push as a second factor. It integrates with Nginx, Caddy, and Traefik via forward-auth — your reverse proxy asks Authelia "is this user allowed?" before forwarding the request to the backend.

This guide deploys Authelia with Docker Compose, configures file-based users with Argon2id password hashing, sets up TOTP 2FA, and protects multiple backend services through Nginx forward-auth. The result is a production-grade authentication layer for any service that does not have built-in SSO support.

How Forward-Auth Works

  • User requests grafana.example.com
  • Nginx sends an auth subrequest to Authelia's verify endpoint
  • If not authenticated → redirect to Authelia login portal
  • User logs in + completes 2FA → Authelia sets a session cookie
  • Nginx receives 200 from verify → forwards request to Grafana

Prerequisites

  • Linux server with Docker and Docker Compose
  • Nginx reverse proxy with TLS (Let's Encrypt)
  • Subdomain for Authelia portal (e.g. auth.example.com)
  • Services to protect (Grafana, Proxmox, internal tools)

Step 1: Create Directory Structure

bash
sudo mkdir -p /opt/authelia/{config,secrets}
cd /opt/authelia

# Generate secrets:
openssl rand -hex 32 | sudo tee secrets/JWT_SECRET
openssl rand -hex 32 | sudo tee secrets/SESSION_SECRET
openssl rand -hex 32 | sudo tee secrets/STORAGE_ENCRYPTION_KEY

Step 2: Create Users Database

bash
# Generate password hash (replace 'yourpassword'):
docker run --rm authelia/authelia:latest authelia crypto hash generate argon2 --password 'yourpassword'

# Create /opt/authelia/config/users_database.yml:
sudo tee config/users_database.yml >/dev/null <<'EOF'
users:
  admin:
    displayname: "Admin User"
    password: "$argon2id$v=19$m=65536,t=3,p=4$PASTE_HASH_HERE"
    email: admin@example.com
    groups:
      - admins
      - dev
EOF

Step 3: Write Authelia Configuration

bash
sudo tee config/configuration.yml >/dev/null <<'EOF'
theme: dark

server:
  address: 'tcp://0.0.0.0:9091'

log:
  level: info

identity_validation:
  reset_password:
    jwt_secret: file:///secrets/JWT_SECRET

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

access_control:
  default_policy: deny
  rules:
    - domain: "grafana.example.com"
      policy: two_factor
      subject: "group:admins"
    - domain: "proxmox.example.com"
      policy: two_factor
      subject: "group:admins"
    - domain: "auth.example.com"
      policy: bypass

session:
  name: authelia_session
  secret: file:///secrets/SESSION_SECRET
  expiration: 12h
  inactivity: 2h
  cookies:
    - domain: example.com
      authelia_url: https://auth.example.com
      default_redirection_url: https://grafana.example.com

storage:
  encryption_key: file:///secrets/STORAGE_ENCRYPTION_KEY
  local:
    path: /config/db.sqlite3

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

regulation:
  max_retries: 5
  find_time: 2m
  ban_time: 10m

totp:
  issuer: example.com
  period: 30
  skew: 1
EOF

Step 4: Docker Compose Stack

bash
sudo tee docker-compose.yml >/dev/null <<'EOF'
services:
  authelia:
    image: authelia/authelia:latest
    container_name: authelia
    restart: unless-stopped
    volumes:
      - ./config:/config
      - ./secrets:/secrets:ro
    ports:
      - "127.0.0.1:9091:9091"
    environment:
      - TZ=Europe/Berlin
EOF

sudo docker compose up -d
sudo docker compose logs -f authelia

Step 5: Nginx Forward-Auth for Protected Services

nginx
# /etc/nginx/snippets/authelia-auth.conf
set $authelia_url http://127.0.0.1:9091;

location /authelia {
    internal;
    proxy_pass $authelia_url/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-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 X-Real-IP $remote_addr;
}

# Protected service block:
server {
    listen 443 ssl http2;
    server_name grafana.example.com;

    auth_request /authelia;
    error_page 401 403 =302 https://auth.example.com/?rd=$scheme://$http_host$request_uri;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
    }
}

Step 6: Nginx Config for Authelia Portal

nginx
server {
    listen 443 ssl http2;
    server_name auth.example.com;

    location / {
        proxy_pass http://127.0.0.1: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: Register TOTP Device

Log in at https://auth.example.com. On first login, Authelia prompts you to register a TOTP device. Scan the QR code with Google Authenticator, Authy, or any TOTP app. Store backup codes securely.

Step 8: Test Protected Service Access

bash
# Open in browser:
# https://grafana.example.com → redirects to auth.example.com
# Login + 2FA → redirects back to Grafana

# Verify from CLI:
curl -I https://grafana.example.com
# Should return 302 to auth.example.com

Access Control Policies

  • bypass — no authentication required (use for auth portal itself).
  • one_factor — password only. Suitable for low-risk internal tools.
  • two_factor — password + TOTP/WebAuthn. Use for admin panels.
  • deny — block access entirely regardless of authentication.

Production Checklist

  • Back up /opt/authelia/config and secrets directory.
  • Use two_factor policy for all admin-facing services.
  • Configure SMTP notifier instead of filesystem for password reset emails.
  • Review access control rules when adding new services.
  • Test login flow after every Authelia upgrade.

"SSO is not just convenience — it is a security control that centralizes authentication, enforces 2FA, and gives you one place to revoke access."

Technical Author

Kacey H.

Kacey H.

Support and guides. Turns the questions that show up in tickets into steps you can follow.