Install Authelia: 2FA + SSO for VPS on Linux
Authelia is an open-source authentication and authorization server that provides Two-Factor Authentication (2FA) and Single Sign-On (SSO) for all applications running on your VPS. It integrates seamlessly with reverse proxies like Nginx and Traefik.
Instead of each application managing its own login system, Authelia acts as a central security gateway — users log in once and gain access to all permitted services, protected by 2FA to prevent unauthorized access.
Why Use Authelia?
| Feature | Details |
|---|---|
| Single Sign-On | Log in once, access all services |
| 2FA/MFA | Supports TOTP (Google Authenticator), WebAuthn, Push notifications |
| Access Control | Fine-grained policies down to path-level |
| Brute-Force Protection | Account lockout after repeated failed logins |
| Open Source | Free, self-hosted, no monthly fees |
Prerequisites
- VPS running Ubuntu 22.04/24.04 or Debian 12
- Docker and Docker Compose installed
- Nginx or Traefik as a reverse proxy
- A domain with Wildcard SSL certificate (e.g.,
*.example.com) - At least 512 MB RAM for Authelia + Redis
Architecture Overview
We'll deploy Authelia with Docker Compose alongside Redis (for sessions), with Nginx handling the reverse proxy and auth_request module.
VPS
├── Nginx (reverse proxy + auth_request)
│ ├── authelia.example.com → Authelia
│ ├── app1.example.com → App1 (protected)
│ └── app2.example.com → App2 (protected)
└── Docker Compose
├── authelia (authentication server)
└── redis (session storage)
Step 1: Create Directory Structure
mkdir -p ~/authelia/{config,data}
cd ~/authelia
Step 2: Create docker-compose.yml
cat > docker-compose.yml << 'EOF'
version: '3.8'
services:
authelia:
image: authelia/authelia:latest
container_name: authelia
restart: unless-stopped
volumes:
- ./config:/config
- ./data:/data
environment:
- AUTHELIA_JWT_SECRET_FILE=/config/jwt_secret
- AUTHELIA_SESSION_SECRET_FILE=/config/session_secret
- AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE=/config/storage_encryption_key
ports:
- "9091:9091"
depends_on:
- redis
networks:
- authelia_net
redis:
image: redis:7-alpine
container_name: authelia_redis
restart: unless-stopped
volumes:
- redis_data:/data
networks:
- authelia_net
volumes:
redis_data:
networks:
authelia_net:
driver: bridge
EOF
Step 3: Generate Secret Files
# Generate random secret keys
openssl rand -hex 64 > ~/authelia/config/jwt_secret
openssl rand -hex 64 > ~/authelia/config/session_secret
openssl rand -hex 64 > ~/authelia/config/storage_encryption_key
chmod 600 ~/authelia/config/*_secret ~/authelia/config/storage_encryption_key
Step 4: Create configuration.yml
cat > ~/authelia/config/configuration.yml << 'EOF'
server:
host: 0.0.0.0
port: 9091
log:
level: info
theme: light
jwt_secret_file: /config/jwt_secret
default_redirection_url: https://example.com
totp:
issuer: example.com
period: 30
skew: 1
authentication_backend:
file:
path: /config/users_database.yml
password:
algorithm: bcrypt
iterations: 12
access_control:
default_policy: deny
rules:
# Allow public access to main website
- domain: "www.example.com"
policy: bypass
# Require 2FA for admin tools
- domain: "*.example.com"
subject: "group:admins"
policy: two_factor
# Require single-factor for regular users
- domain: "app1.example.com"
subject: "group:users"
policy: one_factor
session:
name: authelia_session
secret_file: /config/session_secret
expiration: 1h
inactivity: 15m
domain: example.com
redis:
host: redis
port: 6379
regulation:
max_retries: 5
find_time: 2m
ban_time: 10m
storage:
local:
path: /data/db.sqlite3
encryption_key_file: /config/storage_encryption_key
notifier:
filesystem:
filename: /data/notification.txt
EOF
example.com with your actual domain. For production, use an SMTP notifier instead of filesystem to receive password reset emails.Step 5: Create Users Database
# Generate a password hash
docker run --rm authelia/authelia:latest authelia hash-password 'your_secure_password'
# Output example: $argon2id$v=19$m=65536,t=3,p=4$...
cat > ~/authelia/config/users_database.yml << 'EOF'
users:
admin:
displayname: "Administrator"
password: "$argon2id$v=19$m=65536,t=3,p=4$YOUR_HASH_HERE"
email: [email protected]
groups:
- admins
- users
alice:
displayname: "Alice"
password: "$argon2id$v=19$m=65536,t=3,p=4$ALICE_HASH_HERE"
email: [email protected]
groups:
- users
EOF
Step 6: Configure Nginx
Add the auth_request location block and enable authentication for each virtual host you want to protect.
# /etc/nginx/snippets/authelia.conf
location /internal/authelia/authz {
internal;
proxy_pass http://127.0.0.1:9091/api/authz/auth-request;
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-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
}
# /etc/nginx/sites-available/app1.example.com
server {
listen 443 ssl http2;
server_name app1.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
include /etc/nginx/snippets/authelia.conf;
location / {
auth_request /internal/authelia/authz;
auth_request_set $target_url $scheme://$http_host$request_uri;
error_page 401 =302 https://authelia.example.com/?rd=$target_url;
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
# Authelia's own virtual host
server {
listen 443 ssl http2;
server_name authelia.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/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-Proto $scheme;
}
}
Step 7: Start Authelia
cd ~/authelia
docker compose up -d
# Watch logs
docker compose logs -f authelia
Step 8: Register TOTP (2FA)
On first login, Authelia will prompt you to register a TOTP device. Scan the QR code with Google Authenticator or Authy to complete enrollment.
# Check if a user has registered TOTP
docker exec authelia authelia storage user totp export --config /config/configuration.yml
Verify Authelia Is Working
# Check containers are running
docker compose ps
# Test health endpoint
curl -s http://127.0.0.1:9091/api/health | python3 -m json.tool
# Check Redis connectivity
docker exec authelia_redis redis-cli ping
# Tail Authelia logs in real time
docker compose logs -f authelia --tail=50
# Test auth endpoint (expect 401 - no active session)
curl -I http://127.0.0.1:9091/api/authz/auth-request
docker compose logs authelia. The most common issue is incorrect permissions on config files — run chmod 600 ~/authelia/config/* to fix it.Common Issues
Error: "level=fatal msg=\"configuration: secrets: error loading secret\""
The secret files have wrong permissions or the Authelia container cannot read them.
# Fix permissions
chmod 600 ~/authelia/config/jwt_secret
chmod 600 ~/authelia/config/session_secret
chmod 600 ~/authelia/config/storage_encryption_key
# Restart the container
docker compose restart authelia
Error: "dial tcp redis:6379: connect: connection refused"
The Redis container isn't ready yet, or the service name in docker-compose.yml doesn't match configuration.yml.
# Check Redis service name in docker-compose.yml
docker compose ps
# The name in session.redis.host must match the service name
# Start Redis first
docker compose up -d redis
sleep 3
docker compose up -d authelia
Error: "auth_request module returned 500"
Nginx cannot reach Authelia, or the proxy_pass URL is incorrect.
# Verify Authelia is bound to port 9091
ss -tlnp | grep 9091
# Test Nginx configuration
nginx -t
# Check Nginx error log
tail -50 /var/log/nginx/error.log
Error: "level=error msg=\"session: error reading session\""
The session secret changed after a restart, invalidating all existing sessions.
# Flush all Redis sessions (users must re-login)
docker exec authelia_redis redis-cli FLUSHDB
docker compose restart authelia
Error: "totp: failed to verify code"
The server clock is more than 30 seconds out of sync with the user's device.
# Check server time
timedatectl status
# Enable NTP sync
timedatectl set-ntp true
# Force immediate sync
chronyc makestep
Frequently Asked Questions
How is Authelia different from Keycloak?
Authelia is lightweight and simple — ideal for resource-constrained VPS instances. It uses a config file instead of a database and provides 2FA + SSO directly through the reverse proxy. Keycloak offers more features (full OAuth2 server, SAML) but requires significantly more RAM (512 MB or more) and has a steeper learning curve.
Can Authelia work with Traefik?
Yes, and Traefik supports Authelia natively via the forwardAuth middleware. Simply add the appropriate labels to each service's docker-compose definition instead of editing configuration files manually — making it easier to manage than the Nginx integration.
If Authelia goes down, can users still access protected services?
No. When Nginx's auth_request cannot reach the upstream Authelia instance, it returns an error. To mitigate this, run Authelia on Docker Swarm or set restart: always with health checks in docker-compose so the container automatically restarts on failure.