Want to add a security layer in front of your self-hosted apps without modifying each one? Authelia acts as an authentication gateway that sits between your reverse proxy and your applications, adding SSO, 2FA, and access control to anything you run.
Why Authelia?
Every self-hosted service has its own login system. Jellyfin has one. Grafana has another. Your download client? Another password to remember. Authelia solves this by becoming the single authentication point for your entire homelab.
Here's what you get:
- Single Sign-On (SSO): Log in once, access everything
- Two-Factor Authentication (2FA): TOTP, WebAuthn, or Duo Security
- Access Control: Different rules for different users, IPs, or times
- Per-Service Policies: Require 2FA for admin panels, allow LAN access to media servers
- Lightweight: Single Go binary, minimal resource usage
How It Works
Authelia sits behind your reverse proxy (Nginx, Traefik, Caddy). When someone requests a protected service, the proxy checks with Authelia first. If not authenticated, they're redirected to Authelia's login portal. After successful auth (with optional 2FA), Authelia tells the proxy to let them through.
Internet → Reverse Proxy → Authelia (auth check) → Your App
↓
Not authenticated? → Authelia login portal
Prerequisites
- A VPS or server with Docker installed
- A reverse proxy already configured (Traefik, Nginx, or Caddy)
- A domain name with DNS pointing to your server
- SMTP access for email notifications (optional but recommended)
Minimum specs: 1 CPU, 512MB RAM, 1GB disk. Authelia is extremely lightweight.
Step 1: Create the Directory Structure
mkdir -p ~/authelia/config
cd ~/authelia
Step 2: Create the Docker Compose File
version: "3.8"
services:
authelia:
image: authelia/authelia:latest
container_name: authelia
restart: unless-stopped
ports:
- "9091:9091"
volumes:
- ./config:/config
environment:
- TZ=America/New_York
- AUTHELIA_JWT_SECRET_FILE=/config/secrets/jwt
- AUTHELIA_SESSION_SECRET_FILE=/config/secrets/session
- AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE=/config/secrets/storage
healthcheck:
test: ["CMD", "authelia", "healthcheck"]
interval: 30s
timeout: 3s
retries: 3
Step 3: Generate Secrets
mkdir -p config/secrets
openssl rand -hex 32 | tr -d '
' > config/secrets/jwt
openssl rand -hex 32 | tr -d '
' > config/secrets/session
openssl rand -hex 32 | tr -d '
' > config/secrets/storage
Step 4: Create the Configuration
Create config/configuration.yml:
---
server:
host: 0.0.0.0
port: 9091
log:
level: info
theme: dark
jwt_secret: "" # From file
default_redirection_url: https://auth.yourdomain.com
totp:
issuer: authelia.com
period: 30
skew: 1
webauthn:
disable: false
display_name: Authelia
attestation_conveyance: none
user_verification: preferred
timeout: 60s
authentication_backend:
file:
path: /config/users_database.yml
password:
algorithm: argon2id
iterations: 3
memory: 65536
parallelism: 4
key_length: 32
salt_length: 16
access_control:
default_policy: deny
rules:
# Bypass auth for health checks
- domain: "auth.yourdomain.com"
policy: bypass
# Require 2FA for admin services
- domain: "portainer.yourdomain.com"
policy: two_factor
- domain: "grafana.yourdomain.com"
policy: two_factor
# One factor for media (convenience on LAN)
- domain: "jellyfin.yourdomain.com"
networks:
- 192.168.1.0/24
policy: one_factor
- domain: "jellyfin.yourdomain.com"
policy: two_factor
# Everything else requires at least password
- domain: "*.yourdomain.com"
policy: one_factor
session:
name: authelia_session
secret: "" # From file
expiration: 1h
inactivity: 5m
remember_me_duration: 1M
cookies:
- domain: yourdomain.com
authelia_url: https://auth.yourdomain.com
default_redirection_url: https://yourdomain.com
storage:
encryption_key: "" # From file
local:
path: /config/db.sqlite3
notifier:
smtp:
host: smtp.gmail.com
port: 587
username: [email protected]
password: your-app-password
sender: Authelia
Step 5: Create User Database
Create config/users_database.yml:
---
users:
ali:
displayname: "Ali"
password: "$argon2id$v=19$m=65536,t=3,p=4$..." # Generate below
email: [email protected]
groups:
- admins
- users
Generate a password hash:
docker run authelia/authelia:latest authelia crypto hash generate argon2 --password 'YourSecurePassword'
Copy the output into the password field.
Step 6: Start Authelia
docker compose up -d
docker logs -f authelia
Wait for: Startup complete. Access the portal at https://auth.yourdomain.com (or http://your-server-ip:9091 if testing without a reverse proxy).
Step 7: Configure Your Reverse Proxy
Traefik
# docker-compose.yml labels for protected service
labels:
- "traefik.enable=true"
- "traefik.http.routers.app.rule=Host(`app.yourdomain.com`)"
- "traefik.http.routers.app.middlewares=authelia@docker"
- "traefik.http.middlewares.authelia.forwardauth.address=http://authelia:9091/api/authz/forward-auth"
- "traefik.http.middlewares.authelia.forwardauth.trustForwardHeader=true"
- "traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=Remote-User,Remote-Groups,Remote-Name,Remote-Email"
Nginx
location / {
proxy_pass http://app:3000;
# Authelia auth request
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;
proxy_set_header Remote-User $user;
proxy_set_header Remote-Groups $groups;
proxy_set_header Remote-Name $name;
proxy_set_header Remote-Email $email;
}
location /authelia {
internal;
proxy_pass http://authelia:9091/api/authz/auth-request;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URL $target_url;
}
Verification
- Visit a protected service (e.g.,
https://portainer.yourdomain.com) - You should be redirected to Authelia's login page
- Log in with your username/password
- If 2FA is required, you'll be prompted to register a TOTP app or WebAuthn key
- After authentication, you're redirected to the original service
- Check that the service sees your username in headers (most apps auto-login or pre-fill)
Testing Access Rules
# Test from LAN (should allow 1FA for Jellyfin)
curl -H "Host: jellyfin.yourdomain.com" http://localhost/
# Test from "outside" (simulate with X-Forwarded-For)
curl -H "Host: jellyfin.yourdomain.com" -H "X-Forwarded-For: 8.8.8.8" http://localhost/
Next Steps
- Add more services: Just add domain rules in
access_control - Enable WebAuthn: Use hardware keys (YubiKey) for phishing-resistant 2FA
- LDAP integration: Connect to existing user directories
- File-based secrets: Move secrets to Docker secrets or Vault
- High availability: Run multiple instances with Redis session storage
Related Guides
- Self-Host Traefik: The Ultimate Reverse Proxy
- Self-Host Authentik: Full Identity Provider
- Self-Host Portainer: Docker Management
Authelia gives you enterprise-grade authentication without the enterprise complexity. Your homelab just got a serious security upgrade.