How to Install Authelia on Ubuntu 24.04 VPS — Self-Hosted SSO + 2FA for Your Entire Stack
If you run a self-hosted stack — Nextcloud, Grafana, Portainer, Vaultwarden, Gitea, a handful of internal dashboards — you have a problem. Every app has its own login. Every app has its own password policy. Half of them do not support two-factor authentication at all, and the ones that do force you to enroll separately. This guide walks you through installing Authelia on an Ubuntu 24.04 VPS, from the first docker compose up to a hardened production deployment that puts a single sign-on portal with strong 2FA in front of every service you host.
Not sure which plan to pick? Authelia itself is light, but the stack behind it (Redis, LDAP, your apps) benefits from real cores and RAM. CloudCore Professional gives you 6 vCPU, 12 GB RAM, and 100 GB NVMe — enough for Authelia plus a dozen protected services.
Table of Contents
What is Authelia?
Authelia is an open-source authentication and authorization server that sits in front of your web apps and asks one question: is this request allowed? It is written in Go, ships as a single small binary (or Docker image), and talks the two protocols that matter for web SSO — forward authentication (for reverse proxies like Nginx, Traefik, Caddy, HAProxy) and OpenID Connect 1.0 (for apps that speak OIDC natively).
The pieces you actually interact with are:
- Web portal — a clean login page at
auth.yourdomain.comwhere users enter their password and complete 2FA. After login, they are redirected back to whichever protected app they were trying to reach. - Authentication backend — either a flat file backend (a YAML file with bcrypt or Argon2 password hashes, perfect for 1-50 users) or LDAP (for larger directories, including lldap as a lightweight companion).
- Access control rules — a declarative list that says "anyone on this network can reach Grafana with one factor, but
/adminneeds two factors and must be a member of theadminsgroup." Domains and subdomains, path prefixes, networks, methods, and user groups are all matchable. - Second factor methods — TOTP (Google Authenticator, Aegis, 1Password, Bitwarden), WebAuthn (YubiKey, Solokey, Touch ID, Windows Hello), and Duo push.
- Session storage — Redis in production, with a secure httpOnly cookie scoped to your root domain so every subdomain inherits the session.
- Persistence — SQLite for small deployments or PostgreSQL / MySQL for production — stores TOTP secrets, WebAuthn credentials, user preferences, and audit logs.
- OIDC provider — lets apps like Nextcloud, Grafana, Outline, Gitea, or Proxmox delegate login to Authelia via standard OAuth 2.0 / OIDC flows.
Why Self-Host SSO + 2FA?
The commercial SSO market is aimed at companies with hundreds of seats. Okta, Auth0, and JumpCloud start in the low double digits per user per month — fine for a 200-person SaaS, overkill for a home lab or a two-person agency running a stack of self-hosted tools. Here is what you get by running Authelia on your own VPS:
- One login, dozens of apps — enroll 2FA once. Log in once. Every protected subdomain inherits the session until it expires.
- 2FA on apps that do not support it — Portainer, Vaultwarden's admin panel, Uptime Kuma, Home Assistant, every "we will add SSO in v2" tool ever. Authelia forces 2FA in front of the app, whether the app knows about it or not.
- Zero per-user cost — the container runs for the same EUR 19.99/mo whether you have one user or fifty. No seat math.
- Data stays on your VPS — passwords, TOTP secrets, WebAuthn credentials, and session cookies never leave your server. No third-party identity provider sees any of it.
- OIDC for modern apps, forward-auth for everything else — you do not have to pick. Nextcloud talks OIDC; your random Python dashboard gets forward-auth. Both use the same user database and the same 2FA enrollment.
- Hardware keys are free — WebAuthn/FIDO2 with YubiKey or Solokey costs nothing to enable. Commercial IdPs often put hardware 2FA behind their Enterprise tier.
- Auditable open source — readable Go code, public security advisories, a clear threat model.
Authelia vs. Alternatives
| Tool | Model | Best For | Downsides |
|---|---|---|---|
| Authelia | Forward-auth + OIDC | Small-to-medium self-hosted stacks, reverse-proxy lovers | YAML config, no built-in user portal for self-service beyond 2FA |
| Keycloak | Full IdP (SAML, OIDC, LDAP) | Enterprises, SAML requirements, admin UI | Heavy (JVM, 1 GB+ RAM), steeper setup |
| Authentik | Full IdP with flows UI | Teams that want a GUI for everything | Larger footprint, more moving parts |
| Pocket ID | Passkey-only | WebAuthn-first deployments | Newer, narrower protocol support |
Prerequisites
Before you begin, make sure you have:
- A VPS running Ubuntu 24.04 LTS with root or sudo access
- SSH access to your server
- A registered domain with DNS you control (Cloudflare, Route 53, or similar) — you need at least
auth.yourdomain.comand one app subdomain likeapp.yourdomain.com - Docker and Docker Compose installed — see How to Install Docker on Ubuntu 24.04 and How to Install Docker Compose on Ubuntu 24.04
- A reverse proxy already set up — see How to Install Nginx on Ubuntu 24.04 or How to Install Traefik on Ubuntu 24.04
- At least 1 GB of free RAM (Authelia itself uses ~50 MB; Redis adds another ~20 MB)
- Valid TLS certificates for your domains — Let's Encrypt via Certbot is fine
Recommended Plan: CloudCore Professional>
If you are standing up a new self-hosted stack from scratch, we recommend CloudCore Professional:>
- 6 vCPU cores
- 12 GB RAM
- 100 GB NVMe SSD
- Unmetered bandwidth
- EUR 19.99/month>
This gives you enough headroom for Authelia + Redis + Postgres + five to ten protected apps in Docker with breathing room for backups and log retention.
Connect to your server:
ssh root@your-server-ipStep 1: Update System Packages
sudo apt update && sudo apt upgrade -yReboot if the kernel was updated:
sudo rebootStep 2: Install Docker and Docker Compose
If Docker is not already installed, follow our dedicated guides:
Verify both are working:docker --version
docker compose versionExpected output:
Docker version 27.3.1, build ce12230
Docker Compose version v2.29.7Step 3: Plan Your Domains
Authelia assumes a single root domain shared by the portal and every protected app. The session cookie is set at the root domain so every subdomain gets it. Typical layout:
| Subdomain | What it serves |
|---|---|
auth.yourdomain.com | Authelia portal |
nextcloud.yourdomain.com | Nextcloud (OIDC client) |
grafana.yourdomain.com | Grafana (forward-auth protected) |
portainer.yourdomain.com | Portainer (forward-auth protected) |
*.yourdomain.com works fine) at your VPS via A or CNAME records. Authelia itself does not issue TLS — your reverse proxy does.Step 4: Create the docker-compose.yml
Create a working directory:
sudo mkdir -p /opt/authelia/{config,secrets,redis}
cd /opt/autheliaCreate the Compose file:
sudo tee /opt/authelia/docker-compose.yml > /dev/null <<'EOF' services: authelia: image: authelia/authelia:4.38 container_name: authelia restart: unless-stopped networks: - proxy - internal ports: - "127.0.0.1:9091:9091" volumes: - ./config:/config - ./secrets:/secrets:ro environment: - TZ=Europe/Berlin - AUTHELIA_JWT_SECRET_FILE=/secrets/jwt_secret - AUTHELIA_SESSION_SECRET_FILE=/secrets/session_secret - AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE=/secrets/storage_encryption_key - AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET_FILE=/secrets/reset_password_jwt_secret - AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE=/secrets/smtp_password depends_on: - redis healthcheck: test: ["CMD", "authelia", "healthcheck"] interval: 30s timeout: 5s retries: 3redis: image: redis:7-alpine container_name: authelia-redis restart: unless-stopped networks: - internal volumes: - ./redis:/data command: ["redis-server", "--save", "60", "1", "--loglevel", "warning"]
networks: proxy: external: true internal: driver: bridge EOF
Create the proxy network if your reverse proxy (Nginx container, Traefik) does not already own one:
docker network create proxyIf you run Nginx on the host instead of in a container, drop the proxy network from the Authelia service and keep only the 127.0.0.1:9091 port mapping — Nginx will reach Authelia at http://127.0.0.1:9091.
Generate the Secrets
Authelia refuses to start without JWT, session, and storage encryption secrets. Generate four strong ones:
cd /opt/authelia/secrets128-char random strings (Authelia recommends 64+ for these)
openssl rand -hex 64 | sudo tee jwt_secret > /dev/null openssl rand -hex 64 | sudo tee session_secret > /dev/null openssl rand -hex 64 | sudo tee storage_encryption_key > /dev/null openssl rand -hex 64 | sudo tee reset_password_jwt_secret > /dev/nullSMTP password (replace with your actual app password)
echo 'your-smtp-app-password' | sudo tee smtp_password > /dev/null
sudo chmod 600 /opt/authelia/secrets/*
Step 5: Generate Password Hashes
Authelia stores passwords as Argon2id hashes in the user database. Generate one using the official image — no need to install Authelia on the host:
docker run --rm authelia/authelia:4.38 \
authelia crypto hash generate argon2 --password 'YourStrongPassword123!'Expected output:
Digest: $argon2id$v=19$m=65536,t=3,p=4$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iMCopy the entire $argon2id$... line — you will paste it into users_database.yml in the next step. Repeat for every user you want to create. Never reuse hashes between users.
If you prefer bcrypt (also supported), swap argon2 for bcrypt in the command.
Step 6: Write users_database.yml
sudo tee /opt/authelia/config/users_database.yml > /dev/null <<'EOF' users: alice: disabled: false displayname: "Alice Admin" password: "$argon2id$v=19$m=65536,t=3,p=4$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM" email: [email protected] groups: - admins - dev
bob: disabled: false displayname: "Bob Developer" password: "$argon2id$v=19$m=65536,t=3,p=4$Yg8u9c/aG2u2eBYD$REPLACE_WITH_BOBS_HASH" email: [email protected] groups: - dev EOF sudo chmod 600 /opt/authelia/config/users_database.yml
The groups list is what you reference from access_control rules. Common groups: admins, dev, ops, family, guests.
Step 7: Write configuration.yml
This is the heart of Authelia. Create /opt/authelia/config/configuration.yml:
sudo tee /opt/authelia/config/configuration.yml > /dev/null <<'EOF' ###############################################################Authelia configuration #
###############################################################server: address: "tcp://0.0.0.0:9091"
log: level: info format: text
theme: dark
totp: issuer: yourdomain.com period: 30 skew: 1
webauthn: disable: false display_name: Authelia attestation_conveyance_preference: indirect user_verification: preferred timeout: 60s
identity_validation: reset_password: jwt_lifespan: 5 minutes jwt_algorithm: HS256
authentication_backend: password_reset: disable: false refresh_interval: 5 minutes file: path: /config/users_database.yml watch: true password: algorithm: argon2 argon2: variant: argon2id iterations: 3 memory: 65536 parallelism: 4
access_control: default_policy: deny rules: # Public paths on auth portal itself - domain: auth.yourdomain.com policy: bypass
# Healthcheck for monitoring - domain: "*.yourdomain.com" resources: - "^/api/health$" policy: bypass
# Public marketing site, one factor only - domain: www.yourdomain.com policy: one_factor
# Grafana — dev group, two factor - domain: grafana.yourdomain.com policy: two_factor subject: - "group:dev"
# Portainer — admins only, two factor - domain: portainer.yourdomain.com policy: two_factor subject: - "group:admins"
# Nextcloud — any logged in user, two factor - domain: nextcloud.yourdomain.com policy: two_factor
# Catch-all: two factor required - domain: "*.yourdomain.com" policy: two_factor
session: name: authelia_session same_site: lax inactivity: 15m expiration: 1h remember_me: 1M cookies: - domain: yourdomain.com authelia_url: https://auth.yourdomain.com default_redirection_url: https://www.yourdomain.com redis: host: redis port: 6379
regulation: max_retries: 3 find_time: 2 minutes ban_time: 10 minutes
storage: local: path: /config/db.sqlite3 # For production, prefer postgres: # postgres: # address: tcp://postgres:5432 # database: authelia # username: authelia # password_file: /secrets/postgres_password
notifier: disable_startup_check: false smtp: address: "submissions://smtp.yourdomain.com:465" timeout: 5s username: [email protected] sender: "Authelia <[email protected]>" subject: "[Authelia] {title}" startup_check_address: [email protected] EOF sudo chmod 600 /opt/authelia/config/configuration.yml
Rule Policies Explained
| Policy | Meaning |
|---|---|
bypass | No auth required. Use for health checks, public endpoints, the login page itself. |
one_factor | Username + password only. Fine for low-risk internal tools. |
two_factor | Username + password + TOTP/WebAuthn/Duo. Default for everything that matters. |
deny | Explicitly block. The default_policy. |
Step 8: Start Authelia
cd /opt/authelia
sudo docker compose up -dCheck logs:
sudo docker compose logs -f autheliaExpected output:
authelia | time="2026-04-16T10:00:00Z" level=info msg="Authelia v4.38.x starting"
authelia | time="2026-04-16T10:00:00Z" level=info msg="Storage schema is up to date"
authelia | time="2026-04-16T10:00:00Z" level=info msg="Listening for non-TLS connections on '0.0.0.0:9091'"Verify health:
curl http://127.0.0.1:9091/api/healthExpected output:
{"status":"OK"}Step 9: Integrate with Nginx (Forward Auth)
This is the most common deployment. Nginx asks Authelia "is this request allowed?" via a subrequest before letting it through to the upstream app.
Authelia Portal Server Block
Create /etc/nginx/sites-available/auth.yourdomain.com:
server { listen 443 ssl http2; server_name auth.yourdomain.com;ssl_certificate /etc/letsencrypt/live/auth.yourdomain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/auth.yourdomain.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; } }
Reusable Forward-Auth Snippet
Create /etc/nginx/snippets/authelia.conf:
# Internal Authelia location — used by auth_request
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-Method $request_method;
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-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-URI $request_uri;
proxy_set_header X-Forwarded-Method $request_method;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Content-Type "";
proxy_set_header Cookie $http_cookie;
proxy_redirect http:// $scheme://;
proxy_http_version 1.1;
proxy_cache_bypass $cookie_session;
proxy_no_cache $cookie_session;
proxy_buffers 4 32k;
client_body_buffer_size 128k;
send_timeout 5m;
proxy_read_timeout 240;
proxy_send_timeout 240;
proxy_connect_timeout 240;
}
Snippet to require Authelia auth on a location
Include this inside any location { } you want to protect.
set $target_url $scheme://$http_host$request_uri;
auth_request /internal/authelia/authz;
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;
Redirect to Authelia portal on 401
error_page 401 =302 https://auth.yourdomain.com/?rd=$target_url;Protect an Upstream App (Grafana Example)
/etc/nginx/sites-available/grafana.yourdomain.com:
server { listen 443 ssl http2; server_name grafana.yourdomain.com;ssl_certificate /etc/letsencrypt/live/grafana.yourdomain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/grafana.yourdomain.com/privkey.pem;
include snippets/authelia.conf;
location / { include snippets/authelia.conf; proxy_pass http://127.0.0.1:3000; 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; } }
Enable and reload:
sudo ln -s /etc/nginx/sites-available/auth.yourdomain.com /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/grafana.yourdomain.com /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginxVisit https://grafana.yourdomain.com. You will be redirected to https://auth.yourdomain.com to log in.
Step 10: Alternative — Traefik Middleware
If you use Traefik (How to Install Traefik on Ubuntu 24.04), define Authelia as a ForwardAuth middleware.
/opt/traefik/dynamic/authelia.yml:
http:
middlewares:
authelia:
forwardAuth:
address: "http://authelia:9091/api/authz/forward-auth"
trustForwardHeader: true
authResponseHeaders:
- "Remote-User"
- "Remote-Groups"
- "Remote-Name"
- "Remote-Email"Attach the middleware to any router via Docker labels:
services:
grafana:
image: grafana/grafana:latest
labels:
- "traefik.enable=true"
- "traefik.http.routers.grafana.rule=Host(grafana.yourdomain.com)"
- "traefik.http.routers.grafana.tls.certresolver=letsencrypt"
- "traefik.http.routers.grafana.middlewares=authelia@file"
networks:
- proxyThe same Authelia container serves both Nginx and Traefik deployments — pick whichever reverse proxy matches the rest of your stack.
Step 11: Alternative — Caddy
Caddy supports forward-auth via the caddy-security plugin or the built-in forward_auth directive (v2.6+):
grafana.yourdomain.com { forward_auth authelia:9091 { uri /api/authz/forward-auth copy_headers Remote-User Remote-Groups Remote-Name Remote-Email } reverse_proxy grafana:3000 }
auth.yourdomain.com { reverse_proxy authelia:9091 }
Caddy handles TLS automatically via Let's Encrypt — no Certbot needed.
Step 12: First Login and TOTP Enrollment
https://grafana.yourdomain.com (or any protected app).https://auth.yourdomain.com.alice) and password.two_factor, Authelia prompts for a second factor. First time, click Register device under "One-Time Password".Next login: username + password + 6-digit code. Done.
Step 13: WebAuthn Hardware Keys
WebAuthn (FIDO2) gives you phishing-resistant 2FA with a YubiKey, Solokey, or platform authenticator (Touch ID, Windows Hello).
https://auth.yourdomain.com.You can register multiple keys per user — recommended for redundancy. On subsequent logins, WebAuthn appears as an option alongside TOTP.
Step 14: OIDC Provider (Nextcloud Example)
Some apps — Nextcloud, Gitea, Outline, Proxmox, Grafana Enterprise — speak OpenID Connect natively. Using OIDC instead of forward-auth gives you proper user provisioning and cleaner logout flows.
Enable OIDC in Authelia
Generate a client secret and its hash:
docker run --rm authelia/authelia:4.38 \
authelia crypto hash generate pbkdf2 --variant sha512 --password 'long-random-client-secret'Generate an HMAC and RSA key pair for signing tokens:
openssl rand -hex 64 | sudo tee /opt/authelia/secrets/oidc_hmac_secret > /dev/null
openssl genrsa -out /opt/authelia/secrets/oidc_private_key.pem 4096
sudo chmod 600 /opt/authelia/secrets/oidc_*Add to configuration.yml:
identity_providers:
oidc:
hmac_secret: '{{ secret "/secrets/oidc_hmac_secret" }}'
jwks:
- key: {{ secret "/secrets/oidc_private_key.pem" | mindent 10 "|" | msquote }}
cors:
endpoints:
- authorization
- token
- revocation
- introspection
- userinfo
allowed_origins_from_client_redirect_uris: true
clients:
- client_id: nextcloud
client_name: Nextcloud
client_secret: '$pbkdf2-sha512$...PASTE_HASH_HERE...'
public: false
authorization_policy: two_factor
redirect_uris:
- https://nextcloud.yourdomain.com/apps/user_oidc/code
scopes:
- openid
- profile
- email
- groups
userinfo_signed_response_alg: none
token_endpoint_auth_method: client_secret_basicRestart Authelia:
cd /opt/authelia && sudo docker compose restart autheliaConfigure Nextcloud
Authelia
- Client ID: nextcloud
- Client Secret: the plaintext secret (not the hash)
- Discovery endpoint: https://auth.yourdomain.com/.well-known/openid-configuration
- Scopes: openid profile email groupsClick Login with Authelia on the Nextcloud login page. You are redirected through Authelia, complete 2FA, and land back in Nextcloud already authenticated.
Other OIDC clients (Grafana, Gitea, Outline, Proxmox 8) follow the same pattern — add a client block in Authelia, register redirect URI, point the app at Authelia's discovery endpoint.
Step 15: SMTP for Password Resets and Alerts
Authelia sends mail for three reasons: 2FA enrollment links, password reset links, and security alerts (new device login, banned account). Without working SMTP, users cannot enroll TOTP or reset passwords.
Option A: External SMTP (Mailgun, SendGrid, Postmark, Fastmail)
The notifier.smtp block in configuration.yml already points at submissions://smtp.yourdomain.com:465. Replace with your provider:
notifier:
smtp:
address: "submissions://smtp.mailgun.org:465"
username: [email protected]
sender: "Authelia <[email protected]>"Store the password in /opt/authelia/secrets/smtp_password (already wired in Step 4).
Option B: Local Mail Submission (Postfix relay)
If you run Postfix on the host as a relay, point Authelia at it over the Docker bridge:
notifier:
smtp:
address: "smtp://host.docker.internal:25"
disable_require_tls: trueAdd extra_hosts: ["host.docker.internal:host-gateway"] to the Authelia service in docker-compose.yml.
Verify
cd /opt/authelia && sudo docker compose logs authelia | grep -i smtpOn startup Authelia sends a test email to startup_check_address. If you see msg="Notifier is operating correctly", SMTP is working.
Step 16: Banned Users and Regulation
The regulation block in configuration.yml automatically bans users who fail too many logins:
regulation:
max_retries: 3
find_time: 2 minutes
ban_time: 10 minutesThree failures in two minutes = ten-minute ban. Bans are stored in SQLite/Postgres, survive restarts, and apply per user (not per IP — use fail2ban or your proxy for IP-level blocks).
Manually Disable a User
Edit /opt/authelia/config/users_database.yml:
bob:
disabled: trueFile watch is enabled — the change takes effect within seconds, no restart needed. Set disabled: true when someone leaves; delete the entry entirely once you are sure.
Manually Clear a Ban
sudo docker exec -it authelia \
sqlite3 /config/db.sqlite3 \
"DELETE FROM authentication_logs WHERE username='alice';"
sudo docker compose restart autheliaBackups
Authelia's state lives in three places:
/opt/authelia/config/db.sqlite3 (or your Postgres database) — TOTP secrets, WebAuthn credentials, identity verification tokens, audit log./opt/authelia/config/users_database.yml — users and password hashes./opt/authelia/config/configuration.yml + /opt/authelia/secrets/ — config and secrets. Without the secrets, existing sessions and encrypted storage are unreadable.Simple nightly backup:
sudo tee /etc/cron.daily/authelia-backup > /dev/null <<'EOF'
#!/bin/bash
DEST=/var/backups/authelia
mkdir -p "$DEST"
STAMP=$(date +%Y%m%d)
tar --exclude='./redis' -czf "$DEST/authelia-$STAMP.tar.gz" -C /opt/authelia .
find "$DEST" -name 'authelia-*.tar.gz' -mtime +30 -delete
EOF
sudo chmod +x /etc/cron.daily/authelia-backupShip the archive offsite (restic, rclone, Backblaze B2) — the secrets file is small but non-regenerable for existing 2FA enrollments.
Troubleshooting
| Problem | Cause | Solution |
|---|---|---|
Infinite redirect loop to auth.yourdomain.com | Cookie domain mismatch | Ensure session.cookies[0].domain matches your actual root domain. auth.yourdomain.com must be a subdomain of it, not the domain itself. |
401 Unauthorized from /api/authz/auth-request even after login | Cookie not forwarded | In Nginx, make sure proxy_set_header Cookie $http_cookie is present in the internal location. Check for missing proxy_http_version 1.1. |
Authelia exits immediately with configuration key not expected | Config schema drift between Authelia versions | Check release notes at github.com/authelia/authelia/releases. v4.38 introduced several renames from v4.37. |
unable to connect to redis | Redis container name or network wrong | Service name redis must match session.redis.host. Both containers must share the internal network. |
| TOTP enrollment email never arrives | SMTP misconfigured | docker compose logs authelia \</td><td>grep -i smtp<code>. Check </code>submissions://<code> vs </code>smtp://, port 465 vs 587, and that the password file is readable. |
yaml: unmarshal errors on startup | YAML indentation/typos | docker run --rm -v /opt/authelia/config:/config authelia/authelia:4.38 authelia validate-config --config /config/configuration.yml |
| WebAuthn enrollment says "origin not allowed" | Wrong server.address or webauthn.display_name | WebAuthn origin must exactly match the URL the browser visits (https://auth.yourdomain.com). |
| New device alert emails on every login | session.remember_me too short or session cookies being wiped | Increase remember_me or check your reverse proxy is not stripping the cookie. |
Validate Configuration Before Restarting
sudo docker run --rm \
-v /opt/authelia/config:/config \
-v /opt/authelia/secrets:/secrets:ro \
authelia/authelia:4.38 \
authelia validate-config --config /config/configuration.ymlTail Logs Live
cd /opt/authelia && sudo docker compose logs -f autheliaSet log.level: debug in configuration.yml temporarily when chasing a specific issue — auth-request debugging is much noisier and more useful at debug level.
FAQ
Can Authelia replace Keycloak?
For most self-hosted stacks, yes. Authelia covers OIDC (enough for Nextcloud, Gitea, Grafana, Outline, Proxmox) and forward-auth (for everything else). It does not implement SAML — if you have an app that only speaks SAML, Keycloak or Authentik is a better fit. If all your apps speak OIDC or live behind a reverse proxy, Authelia is lighter, faster, and simpler to operate.
File backend or LDAP?
Use the file backend (YAML with Argon2 hashes) for 1-50 users. It is the fastest to set up, has no external dependencies, and the users file is version-controllable. Move to LDAP (ideally lldap — a lightweight LDAP server purpose-built for this role) when you need a user database shared across multiple apps beyond Authelia, self-service password changes via LDAP clients, or group management from a UI.
How do I add a new protected app?
Three steps: (1) add a rule to access_control in configuration.yml, (2) create a reverse-proxy server block that includes the Authelia snippet, (3) reload both services. No Authelia restart is required if you only edit users_database.yml (file watch is on) — config changes require docker compose restart authelia.
What happens if Authelia goes down?
Every protected app returns 401 and redirects to the (also-down) portal. Run Authelia with restart: unless-stopped (already in our Compose file), set up an uptime monitor against https://auth.yourdomain.com/api/health, and for truly critical paths leave policy: bypass for your monitoring tool's health check endpoints so outside monitoring still works. For HA, run two Authelia containers behind a load balancer with a shared Redis and Postgres.
Can I enforce a security key (WebAuthn) as the only 2FA method?
Not today at the policy level — Authelia lets the user pick between enrolled methods (TOTP, WebAuthn, Duo). You can disable TOTP globally by setting totp.disable: true, forcing WebAuthn-only. The upside: phishing resistance. The downside: every user must own a hardware key or use a platform authenticator.
How does this interact with Cloudflare Access or Tailscale?
Authelia is application-layer auth; Cloudflare Access and Tailscale are network-layer. They stack. Running Cloudflare Access in front of Authelia gives you a DDoS shield and a first gate before a request even reaches your VPS. Tailscale gives you WireGuard network access to your apps, with Authelia handling per-user authorization once the request lands. For most single-admin VPS deployments, Authelia alone is sufficient.
Next Steps
- Add Authelia to all your existing apps — walk through your Nginx or Traefik config, wrap each server block with the forward-auth snippet, and rebuild your
access_controlrules top to bottom. - Move to Postgres for persistence — SQLite is fine for a few users, but Postgres scales better for OIDC deployments with many clients and audit logs. See the commented
postgres:block inconfiguration.yml. - Deploy lldap for shared user directory — lldap gives you a clean web UI for user and group management that Authelia, Nextcloud, Jellyfin, and Vaultwarden can all share.
- Set up uptime monitoring — point Uptime Kuma at
https://auth.yourdomain.com/api/healthand alert on 2xx failures. - Harden the VPS — review our Ubuntu 24.04 security hardening guide and make sure SSH keys, UFW, and unattended-upgrades are all in place.
- Read the upstream docs — the Authelia documentation has deep dives on every config key, and the integration guides cover dozens of apps.
Run Authelia Where It Belongs>
Self-hosted SSO only works if the box it lives on is reliable. Our CloudCore Professional plan gives you 6 vCPU, 12 GB RAM, 100 GB NVMe, and unmetered bandwidth for EUR 19.99/month — enough for Authelia, Redis, Postgres, and a full stack of protected apps.>
- 99.9% uptime SLA
- European and North American data centers
- Full root access — install anything
- Snapshots and backups included>
Deploy Your VPS Now and put SSO in front of everything you host.