Authelia Two-Factor Authentication Gateway
Authelia is a self-hosted authentication and authorization server that adds two-factor authentication (2FA) with TOTP and WebAuthn to any application behind your reverse proxy, without requiring changes to the application itself. This guide covers deploying Authelia, integrating with Nginx, configuring TOTP and WebAuthn 2FA, and setting up access control rules for fine-grained authorization.
Prerequisites
- Docker and Docker Compose installed
- Nginx or Traefik as your reverse proxy
- A domain name with wildcard or multiple subdomains
- SSL certificates for your domains
- SMTP server for email notifications (optional but recommended)
Installing Authelia with Docker
mkdir -p ~/authelia/config && cd ~/authelia
cat > docker-compose.yml << 'EOF'
services:
authelia:
image: authelia/authelia:latest
container_name: authelia
volumes:
- ./config:/config
ports:
- "9091:9091"
environment:
TZ: UTC
restart: unless-stopped
redis:
image: redis:7-alpine
container_name: authelia-redis
volumes:
- redis-data:/data
command: redis-server --requirepass redis_password
restart: unless-stopped
volumes:
redis-data:
EOF
Core Configuration
cat > ~/authelia/config/configuration.yml << 'EOF'
---
theme: light
jwt_secret: a_very_long_jwt_secret_at_least_20_chars
default_redirection_url: https://auth.example.com
server:
host: 0.0.0.0
port: 9091
log:
level: info
totp:
issuer: authelia.com
period: 30
skew: 1
webauthn:
timeout: 60s
display_name: Example Corp
attestation_conveyance_preference: indirect
user_verification: preferred
authentication_backend:
# Use file-based users (simple setup)
file:
path: /config/users.yml
password:
algorithm: argon2id
iterations: 1
key_length: 32
salt_length: 16
memory: 64
parallelism: 8
# Or use LDAP:
# ldap:
# url: ldaps://ldap.example.com
# base_dn: dc=example,dc=com
# username_attribute: uid
# users_filter: (&({username_attribute}={input})(objectClass=inetOrgPerson))
# groups_filter: (&(member=uid={input},ou=users,dc=example,dc=com)(objectClass=groupOfNames))
# user: cn=readonly,dc=example,dc=com
# password: readonly_password
access_control:
default_policy: deny
rules:
- domain: auth.example.com
policy: bypass
- domain: public.example.com
policy: bypass
- domain: "*.example.com"
policy: two_factor
session:
name: authelia_session
secret: a_very_secret_session_key_change_this
expiration: 1h
inactivity: 5m
domain: example.com # Must be the root domain
redis:
host: redis
port: 6379
password: redis_password
regulation:
max_retries: 3
find_time: 2m
ban_time: 5m
storage:
local:
path: /config/db.sqlite3
# For production, use MySQL or PostgreSQL:
# mysql:
# host: mysql
# port: 3306
# database: authelia
# username: authelia
# password: db_password
notifier:
# For production, configure SMTP:
# smtp:
# host: smtp.example.com
# port: 587
# username: [email protected]
# password: smtp_password
# sender: "Authelia <[email protected]>"
# tls:
# skip_verify: false
# For testing — writes to file instead of email
filesystem:
filename: /config/notification.txt
EOF
Create the users file:
# Generate a password hash
docker run --rm authelia/authelia:latest \
authelia crypto hash generate argon2 --password 'your_password'
# Copy the output hash (starts with $argon2id$...)
cat > ~/authelia/config/users.yml << 'EOF'
users:
admin:
displayname: Administrator
password: "$argon2id$v=19$m=65536,t=1,p=8$HASH_OUTPUT_HERE"
email: [email protected]
groups:
- admins
- users
jsmith:
displayname: John Smith
password: "$argon2id$v=19$m=65536,t=1,p=8$ANOTHER_HASH"
email: [email protected]
groups:
- users
EOF
# Start Authelia
cd ~/authelia
docker compose up -d
docker compose logs -f authelia
Nginx Integration
The Nginx integration uses auth_request to verify sessions with Authelia:
# Create a shared Authelia snippet: /etc/nginx/snippets/authelia.conf
sudo tee /etc/nginx/snippets/authelia.conf << 'EOF'
# Authelia auth endpoint
location /authelia {
internal;
set $upstream_authelia http://127.0.0.1:9091/api/authz/auth-request;
proxy_pass $upstream_authelia;
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 Accept-Encoding "";
}
EOF
# Authelia portal — accessible at auth.example.com
sudo tee /etc/nginx/sites-available/authelia << 'EOF'
server {
listen 443 ssl;
server_name auth.example.com;
ssl_certificate /etc/letsencrypt/live/auth.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/auth.example.com/privkey.pem;
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;
}
}
EOF
# Protected application behind Authelia
sudo tee /etc/nginx/sites-available/grafana << 'EOF'
server {
listen 443 ssl;
server_name grafana.example.com;
ssl_certificate /etc/letsencrypt/live/grafana.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/grafana.example.com/privkey.pem;
include /etc/nginx/snippets/authelia.conf;
location / {
auth_request /authelia;
auth_request_set $target_url $scheme://$http_host$request_uri;
error_page 401 =302 https://auth.example.com/?rd=$target_url;
# Pass auth headers to upstream
auth_request_set $user $upstream_http_remote_user;
auth_request_set $groups $upstream_http_remote_groups;
auth_request_set $name $upstream_http_remote_name;
proxy_set_header Remote-User $user;
proxy_set_header Remote-Groups $groups;
proxy_set_header Remote-Name $name;
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
EOF
Traefik Integration
For Traefik, use Authelia as a ForwardAuth middleware:
# In traefik's dynamic configuration
http:
middlewares:
authelia:
forwardAuth:
address: "http://authelia:9091/api/authz/forward-auth"
trustForwardHeader: true
authResponseHeaders:
- "Remote-User"
- "Remote-Groups"
- "Remote-Name"
- "Remote-Email"
routers:
grafana:
rule: "Host(`grafana.example.com`)"
middlewares:
- authelia
service: grafana
Configuring 2FA Methods
TOTP (Time-Based One-Time Password)
After logging in for the first time, Authelia prompts users to enroll a TOTP device:
- Navigate to any protected resource
- Log in with username and password
- Authelia redirects to the 2FA enrollment page
- Scan the QR code with Google Authenticator, Authy, or any TOTP app
- Enter a verification code to confirm enrollment
WebAuthn (Hardware Keys / Passkeys)
- After first login, go to
https://auth.example.com/ - Click Register Security Key
- Insert a YubiKey or use the browser's built-in passkey support
- Follow the browser prompts to register the authenticator
Email OTP (One-Time Password via Email)
# In configuration.yml
identity_validation:
reset_password:
jwt_lifespan: 5m
jwt_algorithm: HS256
# Requires SMTP notifier configured:
notifier:
smtp:
host: smtp.example.com
port: 587
username: [email protected]
password: smtp_password
sender: "Authelia <[email protected]>"
Access Control Rules
Access control rules are evaluated top to bottom, first match wins:
access_control:
default_policy: deny
rules:
# Allow public assets without any auth
- domain: www.example.com
resources:
- "^/public/.*$"
- "^/health$"
policy: bypass
# Admin area requires 2FA and admin group
- domain: admin.example.com
subject:
- "group:admins"
policy: two_factor
# Internal tools require 2FA for users group
- domain:
- grafana.example.com
- prometheus.example.com
- kibana.example.com
subject:
- "group:users"
- "group:admins"
policy: two_factor
# API endpoints require only 1FA (token-based apps)
- domain: api.example.com
resources:
- "^/v1/.*$"
policy: one_factor
# Block everyone else from all other subdomains
- domain: "*.example.com"
policy: deny
Session and Storage Configuration
For production deployments with multiple servers:
session:
name: authelia_session
secret: very_long_secret_here
expiration: 8h
inactivity: 30m
domain: example.com
redis:
host: redis-server
port: 6379
password: redis_password
database_index: 0
storage:
mysql:
host: mysql-server
port: 3306
database: authelia
username: authelia
password: db_password
Troubleshooting
"Authentication failed" with correct credentials
# Check Authelia logs
docker logs authelia --tail 50
# Verify password hash is correct
docker run --rm authelia/authelia:latest \
authelia crypto hash validate --password 'your_password' \
'$argon2id$v=19$m=65536...'
Redirect loop between Nginx and Authelia
# Verify auth.example.com has policy: bypass in access_control
# Authelia portal must not require authentication to access itself
# Check that cookies are set for the correct domain
# domain: example.com (not auth.example.com)
2FA enrollment page never appears
# User may already have 2FA enrolled — reset it in users.yml or via admin API
docker exec authelia authelia users storage -u admin reset-totp --config /config/configuration.yml
TOTP codes always rejected
# Check server time sync (TOTP requires accurate time)
timedatectl status
sudo chronyc -a makestep
Conclusion
Authelia provides a self-hosted, privacy-respecting 2FA authentication gateway that integrates with Nginx and Traefik to protect any web application without code changes. Its flexible access control rules, support for TOTP and WebAuthn, and Redis-backed sessions make it a production-ready authentication layer for securing internal tools and sensitive applications behind your reverse proxy.


