Skip to content

How to Deploy Authelia on a VPS

Updated Sep 2026

verified on Ubuntu 26.04 · Sep 2026
We earn commissions when you shop through the links below. Full disclosure →

Put Authelia in front of your self-hosted apps on a VPS. One small container adds a login portal and two-factor authentication through Caddy's forward_auth, with secrets kept in files and users in a hashed YAML database.

Before you start
  • A small VPS. Authelia itself idles at about 30 MB of RAM
  • A fresh Ubuntu 24.04 or 26.04 server with root/sudo SSH access
  • A domain you control, with subdomains for the portal and each app it protects (Authelia needs a real domain for its session cookie, not a bare IP)
  • Docker Engine + Compose installed (see the base guide below)
Need a box for this guide? Kamatera's free tier lets you spin one up now.Start free on Kamatera → (opens in new tab)

What Authelia is

Authelia is an authentication and authorization server that sits beside your reverse proxy. When a request comes in for a protected app, the proxy first asks Authelia whether that visitor is allowed in. If the visitor has no valid session, Authelia sends them to its login portal. There they sign in with a password and a second factor (TOTP, WebAuthn or push). After that the request goes through to the app. The app itself never handles a login. This pattern is called forward auth.

Authelia is written in Go and licensed Apache-2.0. On our test box, measured idle, it used about 30 MB of RAM and about 500 MB of disk for the image, so it can share a VPS with the apps it protects. It is a gatekeeper, not a full identity platform: users live in a YAML file or an LDAP directory, not in a web admin UI. If you want a web UI for user management, or a full OIDC/SAML provider for many applications, compare Authelia vs Authentik and Pocket ID vs Authelia before you start.

Server sizing

The catalog lists Authelia's minimum at 256 MB RAM, and our measurement confirms it needs little. Size the box for the apps you are protecting, not for Authelia. A 1 GB instance can run Authelia, Caddy and a couple of small apps. Disk use is the image plus a small SQLite database.

Prepare the server

This guide assumes Docker Engine, the Compose plugin and a ufw firewall are set up. If they aren't, start with Docker & Compose on Ubuntu.

Only SSH and the reverse proxy are public. Authelia's port stays on the loopback interface:

sudo ufw allow OpenSSH
sudo ufw allow 80
sudo ufw allow 443
sudo ufw status verbose

DNS. Authelia's session cookie is scoped to a domain. The portal and every protected app must share a parent domain, for example auth.example.com, app.example.com and wiki.example.com under example.com. Create A records for the portal and each app, all pointing at this server.

Where to host itaffiliate disclosure
Hetzner Cloudrun it on
2 vCPU · 4 GB RAM · 80 GB SSD · $23.59/mo
Get Hetzner Cloud (opens in new tab)
Kamaterafree trial
1 vCPU · 1 GB RAM · 20 GB SSD · $4.00/mo
Start free on Kamatera → (opens in new tab)
DigitalOceanalso works on
1 vCPU · 1 GB RAM · 25 GB SSD · $6.00/mo
Deploy on DigitalOcean → (opens in new tab)

Paid link — we earn a commission if you shop through it.

Install Authelia

Create the project directory, with separate folders for configuration and secrets:

mkdir -p ~/authelia/config ~/authelia/secrets && cd ~/authelia

Secrets

Authelia needs three random secrets:

  • the key that signs password-reset tokens;
  • the session secret;
  • the storage encryption key, which encrypts sensitive columns such as TOTP seeds in the database.

Authelia's documentation recommends loading them from files rather than putting them in the configuration. Generate each one once:

for s in jwt session storage; do
  [ -f secrets/$s ] || openssl rand -hex 64 > secrets/$s
done
chmod 600 secrets/*
ls -l secrets

Back up secrets/storage with the database. Without it, the encrypted data in the database can't be read.

The users database

With the file backend, users live in users_database.yml, and passwords are stored as Argon2 hashes. The Authelia image can create the hash. This block generates a random admin password, hashes it, writes the file, and prints the password once. It only runs if the file doesn't exist yet:

if [ ! -f config/users_database.yml ]; then
  ADMIN_PASS=$(openssl rand -base64 18)
  HASH=$(docker run --rm authelia/authelia:4.39 authelia crypto hash generate argon2 --password "$ADMIN_PASS" | sed -n 's/^Digest: //p')
  cat > config/users_database.yml <<EOF
users:
  admin:
    disabled: false
    displayname: 'Admin'
    password: '$HASH'
    email: 'admin@example.com'
    groups:
      - 'admins'
EOF
  echo "Authelia admin password (save it in your password manager now): $ADMIN_PASS"
fi
grep -c argon2id config/users_database.yml

To add users later, run docker run --rm authelia/authelia:4.39 authelia crypto hash generate argon2 interactively, add another entry under users:, and restart the container with docker compose restart authelia. Authelia doesn't pick up changes to the file on its own unless you set watch: true under authentication_backend.file.

Configuration

Write the main configuration file. Replace example.com with your domain before going live:

cat > config/configuration.yml <<'YAML'
server:
  address: 'tcp://:9091'

log:
  level: 'info'

totp:
  issuer: 'example.com'

authentication_backend:
  file:
    path: '/config/users_database.yml'

access_control:
  default_policy: 'deny'
  rules:
    - domain: '*.example.com'
      policy: 'two_factor'

session:
  cookies:
    - name: 'authelia_session'
      domain: 'example.com'
      authelia_url: 'https://auth.example.com'

storage:
  local:
    path: '/config/db.sqlite3'

notifier:
  filesystem:
    filename: '/config/notification.txt'
YAML

The sections that matter most:

  • access_control starts from deny and then allows every subdomain with two_factor. Rules are checked in order, and the first match wins, so put specific rules, such as bypass for a public health endpoint or one_factor for a low-risk app, above the wildcard.
  • session.cookies is where the domain requirement comes from. domain is the parent domain the cookie covers. authelia_url is where visitors without a session are sent.
  • notifier: filesystem writes emails, such as two-factor registration links, to a file instead of sending them. That's fine for a single admin. For more users, replace it with an smtp block.

Compose file

cat > docker-compose.yml <<'YAML'
services:
  authelia:
    image: authelia/authelia:4.39
    container_name: authelia
    restart: unless-stopped
    volumes:
      - ./config:/config
      - ./secrets:/secrets:ro
    environment:
      AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET_FILE: /secrets/jwt
      AUTHELIA_SESSION_SECRET_FILE: /secrets/session
      AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE: /secrets/storage
      TZ: UTC
    ports:
      # Loopback only: Caddy talks to Authelia, the internet doesn't.
      - "127.0.0.1:9091:9091"
YAML

Start it and wait for the health endpoint:

docker compose up -d
timeout 120 bash -c 'until curl -fsS http://127.0.0.1:9091/api/health; do sleep 3; done'; echo
docker compose logs --tail 20 authelia

{"status":"OK"} means the configuration loaded. Authelia checks its configuration at startup and refuses to start on errors, so if the health check never passes, the log says which key is wrong. A secret set both in a file and in configuration.yml also stops it from starting.

HTTPS + domain via Caddy

Authelia doesn't serve TLS itself here. Caddy does, for the portal and for every protected app. Install Caddy as described in Automatic HTTPS with Caddy, then add one block for the portal and one per protected app:

auth.example.com {
    reverse_proxy 127.0.0.1:9091
}

app.example.com {
    forward_auth 127.0.0.1:9091 {
        uri /api/authz/forward-auth
        copy_headers Remote-User Remote-Groups Remote-Email Remote-Name
    }
    reverse_proxy 127.0.0.1:8081
}

Every request to app.example.com now goes through Authelia first. Replace 127.0.0.1:8081 with wherever your app listens. copy_headers passes the signed-in user's name, groups and email on to the app. Apps that support trusted-header SSO can use those headers to log the user in automatically.

If Caddy runs as a container, use service names (authelia:9091) on a shared Docker network instead of 127.0.0.1.

First login and two-factor setup

Open https://app.example.com. You should be redirected to https://auth.example.com. Sign in as admin with the password printed during install. Because the policy is two_factor, Authelia asks you to register a second factor. With the filesystem notifier, the one-time verification code is written to a file instead of an email:

cat ~/authelia/config/notification.txt

Enter the code, then scan the TOTP QR code with an authenticator app or register a security key. After that you land on the protected app.

Securing it

  • Keep default_policy: deny. New subdomains stay closed until a rule allows them.
  • Brute-force protection is on by default. The regulation section bans a user after repeated failed logins. The defaults are reasonable; tune max_retries, find_time and ban_time if you need to.
  • Keep port 9091 on the loopback interface. The forward-auth endpoint trusts the X-Forwarded-* headers the proxy sends. If anyone else can reach Authelia directly, those headers can be forged.
  • Use SMTP before you add other users, so password resets and two-factor registrations reach them.
  • For more than a handful of users, move them from the YAML file to LLDAP or another LDAP directory. Authelia supports an ldap authentication backend.

Backups

Three things together make a complete backup: the configuration (including users_database.yml), the SQLite database with registered devices and TOTP seeds, and the secrets, especially the storage encryption key. Stop the container for a consistent copy of the database. It restarts in seconds:

cd ~/authelia
docker compose stop
tar czf authelia-backup-$(date +%F).tar.gz config secrets docker-compose.yml
docker compose start
ls -lh authelia-backup-*.tar.gz

The archive contains secrets and password hashes. Encrypt it (for example with gpg --symmetric) before you copy it off the server.

Upgrades

The compose file pins the 4.39 release line, so pull picks up patch releases:

cd ~/authelia
docker compose pull
docker compose up -d

To move to a new minor version, back up first, read that release's notes, then change the tag. Authelia sometimes renames or deprecates configuration keys between minor versions. It logs a warning for deprecated keys and still starts, so check docker compose logs authelia after every upgrade.

Troubleshooting

Authelia exits right after starting. Read docker compose logs authelia. The configuration validator names the exact key. Common causes: a secret defined both in a file and in configuration.yml, a session.cookies.domain that doesn't contain the authelia_url host, or a secret file the container can't read.

Redirect loop between the app and the portal. The cookie domain doesn't cover both hosts, or the site is being reached over plain HTTP. The session cookie is secure-only, so everything must go through Caddy's HTTPS sites.

Every request gets "403 Forbidden" after login. No access_control rule matches that host, so default_policy: deny applies. Add a rule for the domain.

Login works, but the app doesn't know who you are. The app has to read the Remote-User header, and Caddy only passes it if it is listed in copy_headers. Many apps need a setting that enables trusted-header or proxy authentication.

Verification + next steps

You're done when:

  • https://auth.example.com loads with a valid certificate;
  • visiting a protected app while signed out sends you to the portal;
  • signing in requires your second factor;
  • the backup archive, with secrets/storage inside it, is stored off the box.

From here, add a rule and a Caddy block for each app you want to protect. Use one_factor only where a password alone is acceptable. If you later need apps to sign in over OIDC rather than forward auth, Authelia also includes an OpenID Connect provider. For a passkey-only alternative, see Pocket ID, and for a heavier, all-in-one identity server see Keycloak.

Next steps

How to self-host Authelia →More self-hosted sso & identity tools →Best VPS for Keycloak →Automatic HTTPS with Caddy →Run Claude Code with Ollama on Your Own VPS →Deploy Coolify on a VPS →How to Deploy Actual Budget on a VPS →How to Deploy AnythingLLM on a VPS →How to Deploy Appwrite on a VPS →How to Deploy Audiobookshelf on a VPS →How to Deploy authentik on a VPS →How to Deploy Baserow on a VPS →How to Deploy Beszel on a VPS →How to Deploy Bitwarden on a VPS →How to Deploy BookStack on a VPS →How to Deploy CapRover on a VPS →How to Deploy Checkmate on a VPS →How to Deploy Directus on a VPS →How to Deploy docker-mailserver on a VPS →How to Deploy Docmost on a VPS →How to Deploy Dokku on a VPS →How to Deploy Dokploy on a VPS →How to Deploy Firefly III on a VPS →How to Deploy Forgejo on a VPS →How to Deploy Gatus on a VPS →How to Deploy Ghostfolio on a VPS →How to Deploy Gitea on a VPS →How to Deploy GitLab on a VPS →How to Deploy GlitchTip on a VPS →How to Deploy Grafana on a VPS →How to Deploy Graylog on a VPS →How to Deploy Headscale on a VPS →How to Deploy Healthchecks on a VPS →How to Deploy Home Assistant on a VPS →How to Deploy Immich on a VPS →How to Deploy Jan on a VPS →How to Deploy Jellyfin on a VPS →How to Deploy Karakeep on a VPS →How to Deploy Keycloak on a VPS →How to Deploy Leantime on a VPS →How to Deploy LibreChat on a VPS →How to Deploy Linkwarden on a VPS →How to Deploy LocalAI on a VPS →How to Deploy Mailcow on a VPS →How to Deploy Mailu on a VPS →How to Deploy Matomo on a VPS →How to Deploy Mattermost on a VPS →How to Deploy Meilisearch on a VPS →How to Deploy Memos on a VPS →How to Deploy n8n on a VPS →How to Deploy Navidrome on a VPS →How to Deploy NetBird on a VPS →How to Deploy Netdata on a VPS →How to Deploy Nextcloud on a VPS →How to Deploy Next.js to a VPS →How to Deploy Nginx Proxy Manager on a VPS →How to Deploy NocoDB on a VPS →How to Deploy ntfy on a VPS →How to Deploy Ollama on a VPS →How to Deploy Open WebUI on a VPS →How to Deploy OpenHands on a VPS →How to Deploy OpenObserve on a VPS →How to Deploy OpenProject on a VPS →How to Deploy Outline on a VPS →How to Deploy Pangolin on a VPS →How to Deploy Paperless-ngx on a VPS →How to Deploy Passbolt on a VPS →How to Deploy Plane on a VPS →How to Deploy Plausible Analytics on a VPS →How to Deploy Pocket ID on a VPS →How to Deploy PocketBase on a VPS →How to Deploy Prometheus on a VPS →How to Deploy Psono on a VPS →How to Deploy Radarr on a VPS →How to Deploy Rocket.Chat on a VPS →How to Deploy SigNoz on a VPS →How to Deploy Sonarr on a VPS →How to Deploy Stalwart on a VPS →How to Deploy Stirling-PDF on a VPS →How to Deploy Supabase on a VPS →How to Deploy Synapse on a VPS →How to Deploy Taiga on a VPS →How to Deploy TeamPass on a VPS →How to Deploy Tinyauth on a VPS →How to Deploy Traefik on a VPS →How to Deploy Trilium on a VPS →How to Deploy Twenty CRM on a VPS →How to Deploy Umami on a VPS →How to Deploy Uptime Kuma on a VPS →How to Deploy Vaultwarden on a VPS →How to Deploy Vikunja on a VPS →How to Deploy wg-easy on a VPS →How to Deploy Wiki.js on a VPS →How to Deploy Zabbix on a VPS →How to Deploy Zitadel on a VPS →How to Deploy Zulip on a VPS →Docker & Compose on Ubuntu 26.04 →Building AI Workflows with n8n →Install Open WebUI with Ollama →Adding AI-Powered Insights to Plausible Analytics →Building AI-Powered Apps with Supabase and pgvector →

We use analytics cookies (Google Analytics, PostHog) to see which guides are useful. No ad networks, no cross-site tracking. See our privacy policy.