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
- Navigate to
https://auth.yourdomain.com - Log in with your credentials
- You’ll be prompted to register a 2FA device
- Scan the QR code with your authenticator app (Aegis, Authy, Google Authenticator)
- 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>
2FA registration link not appearing
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:
- Enable SMTP notifications — Replace filesystem notifier with real email
- Add WebAuthn — Hardware security keys (YubiKey) for strongest 2FA
- Set up LDAP — If you have many users, use LDAP instead of the file backend
- Configure IP-based rules — Allow bypass from your LAN, require 2FA from WAN
- 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.