
This article provides a guide outlining how to deploy Authelia on Debian VPS.
What is Authelia?
Authelia is an open-source authentication and authorization server used to protect web applications with single sign-on, multi-factor authentication, and access-control policies. In practical terms, it sits in front of your apps and decides whether a visitor should be allowed in, challenged for login/MFA, or denied. Authelia describes itself as an IAM-style server and portal that provides MFA and SSO for applications through a web portal.
It is commonly used with reverse proxies such as Nginx, Traefik, Caddy, and HAProxy. The reverse proxy receives the userβs request, asks Authelia whether the user is authorized, and then either forwards the request to the protected application or redirects the user to the Authelia login portal.
A simple example:
User visits app.example.com
|
v
Nginx checks with Authelia
|
+--> Not logged in? Redirect to auth.example.com
|
+--> Logged in and allowed? Show the app
|
+--> Logged in but not allowed? Deny access
Authelia is especially useful for self-hosted services that do not have strong built-in authentication. For example, you might put Authelia in front of:
Portainer Grafana Prometheus phpMyAdmin BookStack Gitea Uptime Kuma internal admin panels private dashboards
Key features include:
Single sign-on across multiple apps Two-factor authentication TOTP support Security keys/passkeys Access control by domain, user, group, or network Reverse proxy integration OpenID Connect provider support File-based or LDAP-backed users SQLite, MySQL, or PostgreSQL storage
In hosting terms, Authelia is like a security gatekeeper for web apps on your VPS. Instead of each application handling authentication separately, Authelia centralizes access control and lets you enforce stronger login requirements across multiple services.
For example, without Authelia:
app1.example.com has its own login app2.example.com has its own login app3.example.com has weak or no login
With Authelia:
auth.example.com handles authentication app1.example.com requires Authelia login app2.example.com requires Authelia login app3.example.com requires Authelia login + MFA
It is a strong fit when you want to secure private web apps, admin panels, internal dashboards, or self-hosted tools behind a unified login system.
Authelia is an open-source authentication and authorization gateway commonly used to add SSO, MFA, and access-control policies in front of self-hosted web applications. It is normally deployed behind a reverse proxy such as Nginx, Traefik, Caddy, or HAProxy. The Nginx integration uses the auth_request module to forward authorization checks to Authelia before proxying users to protected applications.
This guide uses:
- Debian VPS
- Docker Compose
- Nginx reverse proxy
- Letβs Encrypt SSL via Certbot
- Authelia with file-based users
- SQLite storage for a small-to-medium deployment
- TOTP-based two-factor authentication
- Example protected app:
app.example.com - Authelia portal:
auth.example.com
Replace all example domains, usernames, emails, and passwords before using in production.
Deployment Architecture
The final layout will look like this:
User Browser | | HTTPS v Nginx Reverse Proxy | | auth_request check v Authelia Container | | if authenticated v Protected Application
Example hostnames:
auth.example.com -> Authelia login portal app.example.com -> Protected backend application
Authelia manages the login session cookie and determines whether a request should be allowed, denied, or redirected to the login portal. Its session configuration controls the cookie behavior and domains it can authorize.
Prerequisites
You need:
- Debian 12 or newer VPS
- Root or sudo user
- A domain name
- DNS access
- Ports 80 and 443 open
- At least 1 GB RAM, preferably 2 GB+
Create these DNS records before continuing:
auth.example.com A YOUR_SERVER_IP app.example.com A YOUR_SERVER_IP
For this guide, I will use:
- Main domain: example.com
- Authelia domain: auth.example.com
- Protected app: app.example.com
- Server public IP: 203.0.113.10
Compare Debian VPS Plans
How to Deploy Authelia on Debian VPS
To deploy Authelia on Debian VPS, follow the steps outlined below:
-
Update Debian
ssh root@203.0.113.10
Update the system:
apt update apt upgrade -y
Install useful base packages:
apt install -y ca-certificates curl gnupg lsb-release ufw nano openssl
Set the system timezone if needed:
timedatectl set-timezone America/Chicago
Check:
timedatectl
-
Configure Basic Firewall Rules
Allow SSH, HTTP, and HTTPS.
ufw allow OpenSSH ufw allow 80/tcp ufw allow 443/tcp ufw enable
Check status:
ufw status verbose
Expected:
22/tcp ALLOW 80/tcp ALLOW 443/tcp ALLOW
-
Install Docker Engine
Install Dockerβs official repository key:
install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/debian/gpg \ -o /etc/apt/keyrings/docker.asc chmod a+r /etc/apt/keyrings/docker.asc
Add the Docker repository:
echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \ https://download.docker.com/linux/debian \ $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \ > /etc/apt/sources.list.d/docker.list
Install Docker and Docker Compose plugin:
apt update apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Enable Docker:
systemctl enable --now docker
Verify:
docker --version docker compose version
-
Install Nginx and Certbot
apt install -y nginx certbot python3-certbot-nginx
Enable Nginx:
systemctl enable --now nginx
Check:
systemctl status nginx --no-pager
-
Create Authelia Directory Structure
Create a dedicated directory:
mkdir -p /opt/authelia/config mkdir -p /opt/authelia/data mkdir -p /opt/authelia/secrets cd /opt/authelia
Lock down permissions:
chmod 700 /opt/authelia/secrets
Autheliaβs documentation recommends treating values such as secrets, keys, passwords, and tokens as security-sensitive and avoiding accidental disclosure in shared configuration files.
-
Generate Authelia Secrets
Generate the JWT secret for reset-password identity validation:
openssl rand -base64 48 > /opt/authelia/secrets/jwt_secret
Generate the session secret:
openssl rand -base64 48 > /opt/authelia/secrets/session_secret
Generate the storage encryption key:
openssl rand -base64 48 > /opt/authelia/secrets/storage_encryption_key
Set permissions:
chmod 600 /opt/authelia/secrets/*
The storage encryption key is used to encrypt data in the database; Authelia recommends a random value of at least 64 characters, with a minimum length of 20 characters.
-
Create Docker Compose File
Create:
nano /opt/authelia/docker-compose.yml
Paste:
services: authelia: image: authelia/authelia:latest container_name: authelia restart: unless-stopped volumes: - ./config:/config - ./data:/data - ./secrets:/secrets:ro environment: TZ: America/Chicago AUTHELIA_JWT_SECRET_FILE: /secrets/jwt_secret AUTHELIA_SESSION_SECRET_FILE: /secrets/session_secret AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE: /secrets/storage_encryption_key ports: - "127.0.0.1:9091:9091"This binds Authelia only to
127.0.0.1:9091, meaning it is reachable locally by Nginx but not exposed directly to the public internet.Authelia officially supports Docker deployment, and the Docker method is commonly recommended because the Authelia process does not need privileged host access when file permissions are configured correctly.
-
Create the Main Authelia Configuration
Create:
nano /opt/authelia/config/configuration.yml
Paste this starter configuration:
theme: auto server: address: tcp://0.0.0.0:9091 log: level: info totp: issuer: example.com authentication_backend: password_reset: disable: false file: path: /config/users_database.yml identity_validation: reset_password: jwt_secret: '{{ secret "/secrets/jwt_secret" }}' access_control: default_policy: deny rules: - domain: auth.example.com policy: bypass - domain: app.example.com policy: two_factor subject: - group:admins session: cookies: - name: authelia_session domain: example.com authelia_url: https://auth.example.com default_redirection_url: https://app.example.com expiration: 1h inactivity: 5m remember_me: 1M regulation: max_retries: 5 find_time: 2m ban_time: 10m storage: encryption_key: '{{ secret "/secrets/storage_encryption_key" }}' local: path: /data/db.sqlite3 notifier: filesystem: filename: /data/notification.txtImportant notes:
default_policy: denyblocks anything not explicitly allowed.auth.example.comusesbypassso users can access the login portal.app.example.comrequirestwo_factor.domain: example.comallows cookies to work acrossauth.example.comandapp.example.com.- The filesystem notifier is acceptable for testing, but SMTP is better for production.
Autheliaβs documentation specifically notes that file-based notifications are recommended only for testing, not production. For production, configure SMTP so users can receive password-reset and identity-validation messages by email.
-
Generate an Authelia Password Hash
Authelia stores hashed passwords in
users_database.yml.Run this command and enter your desired password when prompted:
docker run --rm -it authelia/authelia:latest \ authelia crypto hash generate argon2
Autheliaβs current CLI supports Argon2 hash generation, with
argon2idas the default variant.The output will look similar to:
Digest: $argon2id$v=19$m=65536,t=3,p=4$...
Copy the full hash beginning with:
$argon2id$
-
Create the Users Database
Create:
nano /opt/authelia/config/users_database.yml
Paste:
users: admin: displayname: "Admin User" password: "$argon2id$v=19$m=65536,t=3,p=4$REPLACE_WITH_REAL_HASH" email: admin@example.com groups: - adminsReplace:
$argon2id$v=19$m=65536,t=3,p=4$REPLACE_WITH_REAL_HASH
with the real hash generated in the previous step.
Set permissions:
chmod 600 /opt/authelia/config/users_database.yml
Authelia supports file and LDAP authentication backends; this guide uses the file backend because it is simple and suitable for a first deployment or small environment.
-
Validate the Authelia Configuration
Authelia validates configuration at startup and can also validate it manually with the
authelia config validatecommand. This checks for invalid keys, renamed keys, invalid values, and conflicting configuration options.Run:
cd /opt/authelia docker run --rm \ -v /opt/authelia/config:/config \ -v /opt/authelia/data:/data \ -v /opt/authelia/secrets:/secrets:ro \ authelia/authelia:latest \ authelia config validate --config /config/configuration.yml
If successful, continue.
If you see errors, fix the reported configuration line before starting Authelia.
-
Start Authelia
cd /opt/authelia docker compose up -d
Check status:
docker ps
View logs:
docker logs authelia --tail=100
You should see Authelia listening on port
9091.Test locally:
curl -I http://127.0.0.1:9091
A response from Authelia confirms the container is reachable from the VPS.
-
Create Nginx Site for Authelia Portal
Create:
nano /etc/nginx/sites-available/auth.example.com
Paste:
server { listen 80; server_name auth.example.com; location / { proxy_pass http://127.0.0.1:9091; proxy_http_version 1.1; proxy_set_header Host $host; 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 X-Real-IP $remote_addr; } }Enable it:
ln -s /etc/nginx/sites-available/auth.example.com /etc/nginx/sites-enabled/ nginx -t systemctl reload nginx
Issue SSL certificate:
certbot --nginx -d auth.example.com
Test:
curl -I https://auth.example.com
Visit:
https://auth.example.com
You should see the Authelia login portal.
-
Create a Reusable Nginx Authelia Auth Snippet
Create:
nano /etc/nginx/snippets/authelia-authrequest.conf
Paste:
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 $remote_addr; 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-Real-IP $remote_addr; } auth_request /internal/authelia/authz; auth_request_set $redirection_url $upstream_http_location; error_page 401 =302 $redirection_url; error_page 403 =403 /authelia-forbidden;Create a simple forbidden response snippet:
nano /etc/nginx/snippets/authelia-forbidden.conf
Paste:
location = /authelia-forbidden { return 403 "Forbidden by Authelia access policy.\n"; }The Nginx
auth_requestflow requires Nginx to handle redirects because the authorization endpoint itself does not automatically redirect the browser; Authelia returns the redirect location for Nginx to use. -
Configure a Protected Application
This example assumes your protected application listens locally on:
127.0.0.1:8080
Create:
nano /etc/nginx/sites-available/app.example.com
Paste:
server { listen 80; server_name app.example.com; include /etc/nginx/snippets/authelia-authrequest.conf; include /etc/nginx/snippets/authelia-forbidden.conf; location / { proxy_pass http://127.0.0.1:8080; proxy_http_version 1.1; proxy_set_header Host $host; 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 X-Real-IP $remote_addr; proxy_set_header Remote-User $upstream_http_remote_user; proxy_set_header Remote-Groups $upstream_http_remote_groups; proxy_set_header Remote-Email $upstream_http_remote_email; proxy_set_header Remote-Name $upstream_http_remote_name; } }Enable:
ln -s /etc/nginx/sites-available/app.example.com /etc/nginx/sites-enabled/ nginx -t systemctl reload nginx
Issue SSL:
certbot --nginx -d app.example.com
Now visit:
https://app.example.com
Expected behavior:
- Nginx asks Authelia whether the request is authorized.
- If you are not logged in, you are redirected to
https://auth.example.com. - You log in.
- You enroll TOTP if needed.
- You are redirected back to
https://app.example.com.
-
Test with a Simple Temporary Backend
If you do not already have an app running on
127.0.0.1:8080, you can start a temporary test server:docker run -d \ --name test-app \ --restart unless-stopped \ -p 127.0.0.1:8080:80 \ nginx:alpine
Then visit:
https://app.example.com
You should be forced through Authelia before seeing the Nginx welcome page.
-
Configure SMTP Notifications for Production
Filesystem notifications write messages to a local text file and are useful for testing, but production deployments should use SMTP.
Replace this section:
notifier: filesystem: filename: /data/notification.txtWith something like:
notifier: smtp: address: submission://smtp.example.com:587 username: authelia@example.com password: "REPLACE_WITH_SMTP_PASSWORD" sender: "Authelia <authelia@example.com>"A better production version is to store the SMTP password in
/opt/authelia/secrets/smtp_passwordand reference it through an Authelia-supported secret method instead of hardcoding it in YAML.After changing the config:
cd /opt/authelia docker compose restart authelia docker logs authelia --tail=100
-
Add More Protected Domains
For each new protected service, add a rule to
configuration.yml.Example:
access_control: default_policy: deny rules: - domain: auth.example.com policy: bypass - domain: app.example.com policy: two_factor subject: - group:admins - domain: dashboard.example.com policy: two_factor subject: - group:admins - domain: status.example.com policy: one_factor subject: - group:admins - group:usersThen restart Authelia:
cd /opt/authelia docker compose restart authelia
Create an Nginx vhost for each app and include:
include /etc/nginx/snippets/authelia-authrequest.conf; include /etc/nginx/snippets/authelia-forbidden.conf;
-
Add Another User
Generate a password hash:
docker run --rm -it authelia/authelia:latest \ authelia crypto hash generate argon2
Edit users:
nano /opt/authelia/config/users_database.yml
Example:
users: admin: displayname: "Admin User" password: "$argon2id$v=19$m=65536,t=3,p=4$ADMIN_HASH" email: admin@example.com groups: - admins john: displayname: "John Smith" password: "$argon2id$v=19$m=65536,t=3,p=4$JOHN_HASH" email: john@example.com groups: - usersRestart:
cd /opt/authelia docker compose restart authelia
-
Enable Automatic SSL Renewal
Certbot normally installs a systemd timer automatically.
Check:
systemctl list-timers | grep certbot
Test renewal:
certbot renew --dry-run
If successful, Letβs Encrypt renewal is configured.
-
Useful Management Commands
Start Authelia:
cd /opt/authelia docker compose up -d
Stop Authelia:
cd /opt/authelia docker compose down
Restart Authelia:
cd /opt/authelia docker compose restart authelia
View logs:
docker logs authelia --tail=100
Follow logs live:
docker logs -f authelia
Validate configuration:
docker run --rm \ -v /opt/authelia/config:/config \ -v /opt/authelia/data:/data \ -v /opt/authelia/secrets:/secrets:ro \ authelia/authelia:latest \ authelia config validate --config /config/configuration.yml
Update Authelia:
cd /opt/authelia docker compose pull docker compose up -d docker image prune -f
Before updates, validate your configuration. Autheliaβs documentation notes that configuration keys can change over time and that validation is useful before upgrades to avoid downtime.
-
Backup Authelia
Back up these directories:
/opt/authelia/config /opt/authelia/data /opt/authelia/secrets
Create a manual backup:
mkdir -p /root/backups tar -czf /root/backups/authelia-$(date +%F).tar.gz \ /opt/authelia/config \ /opt/authelia/data \ /opt/authelia/secrets
Protect the backup because it contains secrets:
chmod 600 /root/backups/authelia-$(date +%F).tar.gz
A complete restore requires:
configuration.yml users_database.yml SQLite database JWT/session/storage secrets
Do not lose the storage encryption key. If you lose it, encrypted Authelia storage data may become unusable.
-
Hardening Recommendations
Use these practices before placing important services behind Authelia:
Use HTTPS only. Keep Authelia bound to 127.0.0.1, not public 0.0.0.0. Use two_factor for sensitive applications. Use SMTP instead of filesystem notifications. Use strong user passwords. Back up /opt/authelia/secrets securely. Keep Docker images updated. Validate configuration before upgrades. Use default_policy: deny. Create explicit allow rules per domain. Limit direct app access so users cannot bypass Nginx.
If your backend application is containerized, avoid publishing it publicly with
0.0.0.0:PORT. Bind it locally:ports: - "127.0.0.1:8080:80"
Or keep it on a private Docker network and proxy to it through Nginx.
-
Troubleshooting
Authelia container keeps restarting
Check logs:
docker logs authelia --tail=200
Validate config:
cd /opt/authelia docker compose down docker run --rm \ -v /opt/authelia/config:/config \ -v /opt/authelia/data:/data \ -v /opt/authelia/secrets:/secrets:ro \ authelia/authelia:latest \ authelia config validate --config /config/configuration.yml
Common causes:
Bad YAML indentation Wrong domain in session cookie Missing storage encryption key Invalid users_database.yml Unreadable secret files Invalid notification configuration
Login works, but redirect back to app fails
Check:
session: cookies: - domain: example.com authelia_url: https://auth.example.com default_redirection_url: https://app.example.comThe cookie domain should normally be the parent domain, such as:
example.com
not:
auth.example.com
when protecting multiple subdomains.
Nginx returns 500 error
Test Nginx config:
nginx -t
Check logs:
tail -n 100 /var/log/nginx/error.log
Make sure this endpoint is reachable locally:
curl -I http://127.0.0.1:9091/api/authz/auth-request
App is accessible without Authelia
The app may be exposed directly.
Check listening ports:
ss -tulpn
If your app is listening publicly, change it to listen only on localhost or a private Docker network.
Bad:
0.0.0.0:8080
Better:
127.0.0.1:8080
TOTP setup email or password reset does not work
If you are using filesystem notification mode, check:
cat /opt/authelia/data/notification.txt
For production, configure SMTP.
-
Example Final File Layout
/opt/authelia/ βββ docker-compose.yml βββ config/ β βββ configuration.yml β βββ users_database.yml βββ data/ β βββ db.sqlite3 β βββ notification.txt βββ secrets/ βββ jwt_secret βββ session_secret βββ storage_encryption_key -
Minimal Production Checklist
Before considering the deployment complete:
[ ] DNS records point to the VPS. [ ] Firewall allows only needed ports. [ ] Docker is installed and enabled. [ ] Authelia is bound to 127.0.0.1:9091. [ ] Nginx serves auth.example.com over HTTPS. [ ] app.example.com redirects unauthenticated users to Authelia. [ ] TOTP enrollment works. [ ] Access rules use default_policy: deny. [ ] SMTP notification is configured for production. [ ] Certbot renewal dry run passes. [ ] /opt/authelia is backed up securely. [ ] Direct backend app access is blocked.
-
Recommended Next Step
For a production hosting environment, the next improvement would be replacing the file user backend with LDAP or another centralized identity source, and replacing SQLite with PostgreSQL if you expect heavier use, multiple administrators, or more advanced identity-provider features. Authelia supports file and LDAP authentication backends, and supports local SQLite, MySQL, and PostgreSQL storage backends.
Conclusion
You now know how to deploy Authelia on Debian VPS.









