SSD Nodes Learn Hosting plans →
Guides Matt ConnorBy Matt Connor

Authelia vs Authentik: which SSO for one VPS

Authelia is a YAML forward-auth portal with 2FA. Authentik is a full identity provider with a UI and Postgres. Here is which one a single VPS needs.

Authelia vs Authentik: the short answer

Choosing between Authelia and Authentik comes down to one question: do you need a login gate, or an identity provider? Authelia is a login portal with 2FA (two-factor authentication). It sits in front of apps that have no login of their own. Authentik is a full identity provider. It has a web admin UI and a database, and apps log in to it over OIDC (OpenID Connect), SAML (Security Assertion Markup Language) or LDAP (Lightweight Directory Access Protocol).

On one VPS with a few users, Authelia is usually enough. Pick Authentik if you want to manage users in a browser, if an app only speaks SAML or LDAP, or if other people will add and remove accounts without touching YAML files.

This guide pins two releases. Both were checked against their release notes as of October 2026: Authelia v4.39.28 (released 2026-09-17) and Authentik 2026.8.3, which is the image tag in Authentik's current Docker Compose file.

What each tool actually is

Both tools do SSO (single sign-on): you log in once, and every protected app trusts that login. They get there in different ways.

Authelia is one Go binary in one container. All of its configuration lives in a YAML file. Users live in a second YAML file, or in an LDAP directory you already run. Authelia reads LDAP. It does not serve it. There is no admin UI. To add a user, you edit users_database.yml. To change a rule, you edit configuration.yml and restart. Its main job is forward auth: before your reverse proxy passes a request to the app, it asks Authelia whether that request is logged in.

Authentik is a Python and Go application. It runs as a server container, a worker container and PostgreSQL. You set everything in a web UI: users, groups, applications and flows. A flow is the sequence of steps a login goes through, for example a username, then a password, then a TOTP (time-based one-time password) code. Authentik can do forward auth too, through an outpost. An outpost is a small proxy component, and one ships embedded in the server. Authentik can also act as an OIDC provider or a SAML provider, and it can act as an LDAP server for apps that only know LDAP.

For a longer tour of Authentik on its own, read the complete guide to self-hosting Authentik for SSO.

Does Authelia support OIDC now?

Yes. Many install guides still call OIDC "beyond scope", so here is the current status. Authelia's documentation says its OpenID Connect 1.0 provider is "part of an open beta". The same page says Authelia is OpenID Certified for the Basic, Implicit, Hybrid, Form Post and Config OP profiles. The beta label is about the configuration format, which can still change between minor versions. It does not mean that logins are unreliable.

What you give up is convenience. Every OIDC client is a block of YAML. The client secret is an argon2 hash that you generate on the command line. The signing key is a file on disk. Authelia's docs list Dynamic Client Registration and Session Management as planned, not shipped.

Authentik 2026.8 went further in the same area. Its release notes say it is OpenID Certified for the provider profiles and also for the RP-Initiated, Front-Channel and Back-Channel logout profiles. The same release added Dynamic Client Registration and token exchange. In Authentik, you create an OIDC client by filling in a form, and the UI shows you the issuer URL to paste into the app.

So Authelia can do OIDC. What you have to decide is whether you are happy to maintain your OIDC clients as YAML.

What Authentik 2026.8 needs to run

Authentik's Docker Compose install docs ask for "a host with at least 2 CPU cores and 2 GB of RAM". The current compose.yml defines three services: postgresql (the postgres:16-alpine image), server and worker.

Redis is no longer needed. The 2025.10 release notes say "authentik no longer uses Redis at all". Caching, the task queue, the embedded outpost's sessions and WebSocket connections all moved into PostgreSQL. The cost is more load on the database: the same notes expect "roughly 50% more database connections to Postgres". Older guides that tell you to run a Redis container for Authentik are out of date. If you upgrade an older stack, you can remove the Redis service and its configuration.

One change in 2026.8 matters for anyone behind a reverse proxy. The server now trusts X-Forwarded-Proto, X-Forwarded-Host and X-Forwarded-For only when the connection comes from an address listed in AUTHENTIK_LISTEN__TRUSTED_PROXY_CIDRS. The default list covers loopback and the private ranges. That includes 172.16.0.0/12, which Docker networks use, so a proxy on the same Docker host is trusted. A proxy on another machine with a public IP is not. The release notes describe the symptom: Authentik treats HTTPS requests as HTTP, which leads to blocked mixed content, an endless loading spinner, or authentication errors.

Here is the install itself, from the official docs:

mkdir authentik && cd authentik
wget https://docs.goauthentik.io/compose.yml
echo "PG_PASS=$(openssl rand -base64 36 | tr -d '\n')" >> .env
echo "AUTHENTIK_SECRET_KEY=$(openssl rand -base64 60 | tr -d '\n')" >> .env
docker compose pull
docker compose up -d

Then open http://<your server IP>:9000/if/flow/initial-setup/ and set the password for the akadmin user. Do this right away. Until you do, anyone who can reach port 9000 can claim the admin account. A safer approach is to bind the ports to 127.0.0.1, or to put the reverse proxy in front, before you start the stack.

What Authelia needs to run

Authelia needs one container and a config directory. By default it keeps sessions in memory, and Redis is optional. Without Redis, restarting the Authelia container logs every user out, because the sessions lived in that process's memory. On one VPS this is usually acceptable. Persistent data, such as registered 2FA devices and user preferences, goes to a SQLite file.

First, generate three random secrets. Run this command three times and keep each value:

docker run --rm authelia/authelia:4.39.28 authelia crypto rand --length 64 --charset alphanumeric

Then hash a password for your first user. The command asks you to type the password, so it needs -it:

docker run --rm -it authelia/authelia:4.39.28 authelia crypto hash generate argon2

Create authelia/configuration.yml. This is a minimal working file for one domain:

server:
  address: 'tcp://:9091'

totp:
  issuer: 'example.com'

identity_validation:
  reset_password:
    jwt_secret: 'FIRST_RANDOM_VALUE'

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

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

session:
  secret: 'SECOND_RANDOM_VALUE'
  cookies:
    - domain: 'example.com'
      authelia_url: 'https://auth.example.com'
      default_redirection_url: 'https://www.example.com'

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

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

Rules are checked from the top down, and the first match wins. That is why the narrow status.example.com rule sits above the wildcard. default_policy: 'deny' means that any host you forgot to list is blocked, which is the safe way to fail.

Create authelia/users_database.yml with the hash from the previous step:

users:
  alice:
    disabled: false
    displayname: 'Alice'
    password: '$argon2id$v=19$m=65536,t=3,p=4$PASTE_THE_REST_OF_THE_HASH'
    email: 'alice@example.com'
    groups:
      - 'admins'

The filesystem notifier writes emails to a text file instead of sending them. When you register a TOTP device, Authelia asks for a one-time code to prove that you own the account. That code appears in authelia/notification.txt. Read it with cat authelia/notification.txt. Switch to the smtp notifier before you add users who cannot read files on the server.

Forward auth behind Traefik

In Traefik, forward auth is a middleware. You define it once and attach it to each router you want to protect. The Authelia container must share a Docker network with Traefik. The entrypoint and certificate resolver names below must match your own Traefik setup.

services:
  authelia:
    image: authelia/authelia:4.39.28
    container_name: authelia
    restart: unless-stopped
    volumes:
      - ./authelia:/config
    labels:
      traefik.enable: 'true'
      traefik.http.routers.authelia.rule: 'Host(`auth.example.com`)'
      traefik.http.routers.authelia.entrypoints: 'websecure'
      traefik.http.routers.authelia.tls.certresolver: 'letsencrypt'
      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-Email,Remote-Name'

To protect an app, add one label to its router:

      traefik.http.routers.status.middlewares: 'authelia@docker'

Run docker compose up -d, then open https://status.example.com in a private browser window. If the setup is healthy, you are redirected to https://auth.example.com, and after login you are sent back to the app. If the app loads with no login prompt, the middleware label is on the wrong router, or the router name in the label does not match.

Authentik uses the same Traefik mechanism with a different address, http://<authentik server>:9000/outpost.goauthentik.io/auth/traefik, and a longer list of X-authentik-* response headers. It also needs the path /outpost.goauthentik.io/ on the app's host routed to Authentik. In the UI, you need a Proxy Provider and an Application. The full walk-through is in setting up Authentik forward auth with Traefik. It also covers the domain-level mode, which protects every subdomain with one provider.

Forward auth behind nginx

nginx does forward auth with its auth_request module, and Ubuntu's nginx package includes it. Check with this command. It should print http_auth_request_module:

nginx -V 2>&1 | grep -o http_auth_request_module

For Authelia, publish its port on loopback only (127.0.0.1:9091:9091 in the compose file). auth.example.com also needs its own nginx server block that proxies to http://127.0.0.1:9091. Then create /etc/nginx/snippets/authelia-location.conf. This is a shortened version of Authelia's documented snippet, with the upstream pointed at loopback:

set $upstream_authelia http://127.0.0.1:9091/api/authz/auth-request;

location /internal/authelia/authz {
    internal;
    proxy_pass $upstream_authelia;
    proxy_set_header X-Original-Method $request_method;
    proxy_set_header X-Original-URL $scheme://$host$request_uri;
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_set_header Content-Length "";
    proxy_set_header Connection "";
    proxy_pass_request_body off;
    proxy_http_version 1.1;
}

Then create /etc/nginx/snippets/authelia-authrequest.conf:

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-Email $email;
proxy_set_header Remote-Name $name;
auth_request_set $redirection_url $upstream_http_location;
error_page 401 =302 $redirection_url;

Include both snippets in the server block of each protected app, next to your existing ssl_certificate lines:

server {
    listen 443 ssl;
    server_name status.example.com;

    include /etc/nginx/snippets/authelia-location.conf;

    location / {
        include /etc/nginx/snippets/authelia-authrequest.conf;
        proxy_pass http://127.0.0.1:3001;
    }
}

Test and reload with sudo nginx -t && sudo systemctl reload nginx. nginx -t should report syntax is ok and test is successful. You may see a bare 401 Authorization Required page instead of a redirect. That happens when the error_page 401 =302 line is missing from that location, so nginx has no redirect to send.

Authentik's nginx setup has the same shape with different paths. You need a Proxy Provider in forward auth mode and an Application in the UI. The location / block gets auth_request /outpost.goauthentik.io/auth/nginx; and a named error location. The outpost path must be reachable without auth:

location / {
    auth_request     /outpost.goauthentik.io/auth/nginx;
    error_page       401 = @goauthentik_proxy_signin;
    auth_request_set $auth_cookie $upstream_http_set_cookie;
    add_header       Set-Cookie $auth_cookie;
    auth_request_set $authentik_username $upstream_http_x_authentik_username;
    proxy_set_header X-authentik-username $authentik_username;
    proxy_pass http://127.0.0.1:3001;
}

location /outpost.goauthentik.io {
    proxy_pass              http://127.0.0.1:9000/outpost.goauthentik.io;
    proxy_set_header        Host $host;
    proxy_set_header        X-Original-URL $scheme://$host$request_uri;
    add_header              Set-Cookie $auth_cookie;
    auth_request_set        $auth_cookie $upstream_http_set_cookie;
    proxy_pass_request_body off;
    proxy_set_header        Content-Length "";
}

location @goauthentik_proxy_signin {
    internal;
    add_header Set-Cookie $auth_cookie;
    return 302 /outpost.goauthentik.io/start?rd=$scheme://$host$request_uri;
}

The official example passes more X-authentik-* headers, including groups, email, name and uid. Copy the ones your app reads. The Host header sent to the outpost must match the external URL of the app, because the outpost uses it to choose the right provider.

OIDC on each side, for apps that have a real login

Forward auth is the right tool for apps with no login, like a status page or a simple admin panel. Apps with their own user system, such as Nextcloud, Gitea or Grafana, should log in over OIDC instead. If you put forward auth in front of them, users have to log in twice in a row.

In Authelia, each OIDC client is a YAML block. First, generate an RSA signing key into the config directory:

docker run --rm -u "$(id -u):$(id -g)" -v "$PWD/authelia:/config" authelia/authelia:4.39.28 authelia crypto pair rsa generate --directory /config/keys

That writes private.pem and public.pem. Then add a provider block that loads the private key through Authelia's template filter:

identity_providers:
  oidc:
    hmac_secret: 'FOURTH_RANDOM_VALUE'
    jwks:
      - key_id: 'main'
        algorithm: 'RS256'
        use: 'sig'
        key: {{ secret "/config/keys/private.pem" | mindent 10 "|" | msquote }}
    clients:
      - client_id: 'grafana'
        client_secret: '$argon2id$v=19$m=65536,t=3,p=4$PASTE_HASH'
        redirect_uris:
          - 'https://grafana.example.com/login/generic_oauth'
        scopes:
          - 'openid'
          - 'profile'
          - 'email'
          - 'groups'
        authorization_policy: 'two_factor'

The secret template line is expanded only if the container has the environment variable X_AUTHELIA_CONFIG_FILTERS=template. Without it, the line is never expanded, so the key is never loaded. Add that variable to the compose file. The app gets the plain client secret. Authelia keeps only its hash, which you create with the same crypto hash generate argon2 command you used for user passwords.

In Authentik, you create a Provider of type OAuth2/OpenID and an Application in the UI. Each application has its own issuer, in the form https://authentik.example.com/application/o/<application slug>/. If an app needs SAML or an LDAP bind, Authentik is the only one of the two that can provide it. For a wider comparison of identity providers, see how Keycloak, Authentik and Zitadel compare as identity providers.

Memory use: measure it on your own server

This guide gives no idle memory figures for the two stacks. A number measured on someone else's plan, with someone else's flows and user count, tells you little about your own setup. The documented floor is clear enough. Authentik asks for 2 GB of RAM across its server, worker and PostgreSQL. Authelia runs as a single process with a SQLite file, so its footprint is one container.

Measure both on your own VPS after a few minutes of idle time:

docker stats --no-stream --format 'table {{.Name}}\t{{.MemUsage}}\t{{.CPUPerc}}'

For Authentik, add the three container lines together. If the total leaves too little room for the apps you wanted to protect, that settles the question.

What switching from one to the other costs

For forward auth, switching is cheaper than it looks. For everything else, it is more work than it looks.

Forward auth. If every app router points at a middleware with a neutral name, such as sso@docker, switching means changing the middleware definition in one place. If you used the name authelia@docker on twenty routers, you edit twenty labels. The header names also differ. Authelia sends Remote-User, and Authentik sends X-authentik-username. Any app set up for trusted-header login must be told the new header name. Until then, it will see no user name on any request.

OIDC clients. Every client needs a new issuer URL, client ID and secret. Authelia has one issuer for the whole server. Authentik has one issuer per application. Some apps use the sub claim, which is the user's subject ID, as the account key. Those apps may create a new, empty account on the first login after the switch, because the new provider sends a different subject ID for the same person. Check how each app links accounts before you switch it over.

Users and 2FA. Plan for users to set a new password and to register their TOTP app again. Authelia keeps users in YAML, and it stores 2FA secrets encrypted in its own SQLite file. We have not verified an import path from either of these into Authentik's database, so treat re-registration as part of the migration.

Run both side by side during the move. They can share one VPS on different subdomains. Then move one app at a time by changing its middleware or its OIDC settings.

When Authelia is enough

Authelia is enough when all of these are true:

  • You are the admin, and the user list is short and changes rarely.
  • The apps you protect either have no login of their own or speak OIDC.
  • You are comfortable keeping access rules in a YAML file under version control.
  • You would rather spend the VPS's RAM on the apps themselves.

Choose Authentik when any of these are true:

  • Someone who should not edit server files needs to add or remove users.
  • An app needs SAML, or an LDAP server to bind against.
  • You want sign-up, invitations or password recovery flows that you build in a UI.
  • You need features like the privileged access requests added in 2026.8, where a user asks for time-limited access and an approver grants it.

There is also a smaller third option. It fits when your apps already speak OIDC and you mainly need a login page in front of one or two internal tools: putting OAuth2 Proxy in front of an app, connected to an identity provider you already use. And if you want SSO mainly because an app only offers it on a paid plan, read which self-hosted apps charge an SSO tax before you build around it.

FAQ

Is Authelia or Authentik better for a single VPS?

For one admin and a few users protecting apps that have no login of their own, Authelia is simpler. It needs one container and two YAML files, and it keeps its state in SQLite. Authentik is the better choice when other people manage users, or when an app needs SAML or an LDAP server. Authentik's install docs ask for at least 2 CPU cores and 2 GB of RAM.

Does Authentik still need Redis?

No. Since release 2025.10, Authentik "no longer uses Redis at all". Caching, tasks, sessions and WebSocket connections moved into PostgreSQL. The current Docker Compose file runs only postgresql, server and worker. Expect roughly 50% more PostgreSQL connections than an older setup that used Redis.

Can Authelia be an OpenID Connect provider?

Yes. Authelia's OpenID Connect 1.0 provider is officially an open beta, and it is OpenID Certified for the Basic, Implicit, Hybrid, Form Post and Config OP profiles. You define clients in YAML with a hashed client secret, and the signing key is a file on disk. There is no UI for adding clients.

Why does Authentik show an endless loading spinner behind my reverse proxy after upgrading to 2026.8?

From 2026.8, Authentik trusts forwarded headers such as X-Forwarded-Proto only from addresses listed in AUTHENTIK_LISTEN__TRUSTED_PROXY_CIDRS. If your proxy connects from an address outside that list, Authentik treats HTTPS requests as HTTP. The result is blocked mixed content, an endless spinner, or login errors. Add the proxy's address range to that variable and restart the server container.