Implementing Zero-Trust Security with Authelia Behind a Reverse Proxy
I've been running self-hosted services for years, and the moment I added Authelia to my stack was transformative. Before that, I relied on obscure ports and IP allowlists—which felt secure but were fragile. Now? Every request to every internal service is authenticated. Every session is verified. That's the zero-trust model, and it's easier to implement than you might think.
This guide walks you through deploying Authelia behind a reverse proxy (I'll show both Caddy and Nginx examples), configuring multi-factor authentication, and securing your entire homelab with minimal effort. By the end, you'll have a bulletproof authentication layer that works whether you're accessing Nextcloud from your phone or monitoring Uptime Kuma from a random coffee shop.
Why Zero-Trust Matters for Self-Hosters
Traditional network security relies on a "trust but verify" model—your internal network is assumed safe, and external access is restricted. That works until someone on your WiFi isn't who you think they are, or an attacker pivots from a compromised service to another running on the same machine.
Zero-trust flips this: nothing is trusted by default, regardless of where the request comes from. Every access requires authentication. Every session can be revoked. This is especially critical when you're running services like Nextcloud, Vaultwarden, or Jellyfin across a VPS or your home network.
Authelia is the perfect tool for this because it acts as a central authentication and authorization gateway. It sits in front of your reverse proxy and intercepts every request, checking credentials before allowing access to your applications.
Architecture: How Authelia Fits Into Your Stack
Here's the flow I use:
- User → Reverse Proxy (Caddy or Nginx) → Authelia (auth check) → Backend Service (Nextcloud, Jellyfin, etc.)
The reverse proxy receives the request, forwards it to Authelia for authentication, and only passes it through if Authelia gives the green light. Sessions are stored in Redis, and multi-factor authentication (TOTP, WebAuthn) is optional but highly recommended.
I prefer this architecture over embedding authentication in each application because it's centralized, consistent, and I can rotate auth policies without touching my actual services.
Prerequisites
- A VPS or homelab with Docker and Docker Compose installed (RackNerd offers VPS starting around $40/year, perfect for this)
- A domain you control (e.g.,
example.com) - Caddy or Nginx as your reverse proxy
- Redis for session storage
- About 20 minutes and a terminal
Step 1: Deploy Authelia with Docker Compose
I'm using Docker Compose because it handles all the dependencies in one file. Here's my complete setup:
version: '3.8'
services:
authelia:
image: authelia/authelia:latest
container_name: authelia
ports:
- "9091:9091"
environment:
TZ: America/Denver
volumes:
- /path/to/config/authelia:/config
- /path/to/config/authelia/db.sqlite3:/config/db.sqlite3
networks:
- proxy
depends_on:
- redis
restart: unless-stopped
redis:
image: redis:7-alpine
container_name: authelia-redis
command: redis-server --requirepass your_redis_password --appendonly yes
volumes:
- redis-data:/data
networks:
- proxy
restart: unless-stopped
networks:
proxy:
external: true
volumes:
redis-data:
Create the external network first:
docker network create proxy
Then create your config directory and save this as docker-compose.yml. The key volumes are /path/to/config/authelia—change this to your actual path.
Step 2: Create the Authelia Configuration File
Create /path/to/config/authelia/configuration.yml. This is where the magic happens:
---
jwt_secret: use_a_random_64_char_string_here
default_redirection_url: https://example.com
default_2fa_method: totp
server:
host: 0.0.0.0
port: 9091
path_prefix: /authelia/
session:
secret: another_random_64_char_string_here
name: authelia_session
domain: example.com
lifetime: 1h
inactivity: 15m
remember_me: 1M
redis:
host: redis
port: 6379
password: your_redis_password
database_index: 0
authentication_backend:
file:
path: /config/users_database.yml
access_control:
default_policy: deny
rules:
- domain: "*.example.com"
policy: two_factor
- domain: "admin.example.com"
policy: two_factor
subject: "group:admins"
regulation:
max_retries: 3
find_time: 10m
ban_time: 30m
notifier:
filesystem:
filename: /config/notification.txt
password_policy:
standard:
enabled: true
min_length: 8
require_uppercase: true
require_lowercase: true
require_numbers: true
require_special: true
openssl rand -base64 32. Never use placeholder values in production. Authelia will fail silently if these are too short or insecure.Now create /path/to/config/authelia/users_database.yml with your user accounts:
users:
admin:
displayname: "Admin User"
password: "$argon2id$v=19$m=65540,t=3,p=4$your_hashed_password_here"
email: [email protected]
groups:
- admins
user:
displayname: "Regular User"
password: "$argon2id$v=19$m=65540,t=3,p=4$user_hashed_password_here"
email: [email protected]
groups:
- users
Generate password hashes using Authelia's CLI (or use an online Argon2 generator, though I don't recommend sharing passwords online). Once your container is running, you can use:
docker exec authelia authelia crypto hash generate argon2 --password "your_plain_password"
Copy the hash output into the users file.
Step 3: Configure Your Reverse Proxy
This is where your reverse proxy (Caddy or Nginx) sends requests to Authelia for verification. I prefer Caddy because the syntax is cleaner:
Caddy example (Caddyfile):
nextcloud.example.com {
forward_auth localhost:9091 {
uri /authelia/api/verify?rd=https://auth.example.com
copy_headers Remote-User Remote-Groups Remote-Name Remote-Email
}
reverse_proxy localhost:8080
}
jellyfin.example.com {
forward_auth localhost:9091 {
uri /authelia/api/verify?rd=https://auth.example.com
copy_headers Remote-User Remote-Groups Remote-Name Remote-Email
}
reverse_proxy localhost:8096
}
auth.example.com {
reverse_proxy localhost:9091
}
Nginx example (simplified):
upstream authelia {
server localhost:9091;
}
server {
listen 80;
server_name nextcloud.example.com;
location / {
auth_request /authelia/api/verify;
auth_request_set $user $upstream_http_remote_user;
auth_request_set $groups $upstream_http_remote_groups;
proxy_pass http://localhost:8080;
proxy_set_header Remote-User $user;
proxy_set_header Remote-Groups $groups;
}
location /authelia {
proxy_pass http://authelia;
}
}
Step 4: Start Everything and Test
Start your Authelia stack:
cd /path/to/config && docker-compose up -d
Check the logs:
docker-compose logs -f authelia
Visit `https://nextcloud.example.com`. You should be redirected to the Authelia login page. Log in with the credentials you set in `users_database.yml`.
If you configured TOTP (two-factor authentication), Authelia will prompt you to scan a QR code with Google Authenticator or Authy. Save those codes somewhere safe.
Step 5: Fine-Tune Access Control
Authelia's access control is powerful. Update your `configuration.yml` to be more granular:
access_control:
default_policy: deny
rules:
# Admin only: Vaultwarden admin panel
- domain: "vault.example.com"
path: "/admin"
policy: two_factor
subject: "group:admins"
# Regular users: Nextcloud
- domain: "nextcloud.example.com"
policy: two_factor
subject: "group:users"
# Public but requires MFA: Jellyfin
- domain: "jellyfin.example.com"
policy: two_factor
# Deny everything else
- policy: deny
After updating, restart Authelia:
docker-compose restart authelia
Session Management and Logout
One thing I love about Authelia is session control. Users can log out by visiting `https://auth.example.com/logout`. Sessions are stored in Redis with automatic expiration (I set mine to 1 hour of inactivity, 1 month remember-me).
If you need to force logout all users (e.g., after a security incident), flush Redis:
docker exec authelia-redis redis-cli -a your_redis_password FLUSHDB
Monitoring and Troubleshooting
Check Authelia logs for auth failures:
docker-compose logs authelia | grep "authentication failed"
If users are getting 401 errors randomly, it's usually session expiration or Redis connection issues. Verify Redis is running:
docker exec authelia-redis redis-cli -a your_redis_password PING
Should respond with `PONG`.
Scaling Across Multiple Services
The beauty of this setup is scalability. Want to add Vaultwarden, Uptime Kuma, and Immich? Just add more rules to your reverse proxy's forward_auth config. They all use the same Authelia instance, the same user database, the same MFA settings.
I'm currently running 12 services behind one Authelia instance with negligible overhead.
Next Steps
From here, I recommend:
- Enable WebAuthn for hardware key support (FIDO2/U2F security keys)
- Set up OIDC if you want other apps to delegate auth to Authelia
- Implement DUO or another SMS-based second factor for extra paranoia
- Back up your Redis data regularly to avoid losing sessions
Zero-trust security doesn't have to be complex. Authelia makes it accessible for anyone running self-hosted infrastructure. Once you've got it working, you'll sleep better knowing every request to your services is authenticated and logged.
Discussion