Why centralize authentication on your VPS
When you accumulate self-hosted tools (Grafana, Nextcloud, Gitea, an internal dashboard), each brings its own user base and its own password policy. It's a nightmare to maintain and a multiplied attack surface. Authentik solves this by acting as an identity provider (IdP): it supports OAuth2/OIDC, SAML, LDAP and above all forward auth, which lets you protect any application behind a reverse proxy, even those with no notion of login at all. Self-hosting on a VPS guarantees that your credentials never pass through a third-party service and that you stay in control of the user tree and MFA policies.
What you gain with Authentik
- A single set of credentials for all your applications, with centralized sign-out.
- MFA (TOTP, WebAuthn/FIDO2 keys, backup codes) applied uniformly through policy.
- Forward auth to protect "login-less" apps, which is impossible with a classic IdP.
- Fully customizable flows (sign-up, recovery, captcha, access conditions).
- OAuth2/OIDC, SAML and LDAP connectors to plug in modern and legacy apps alike.
- Instant access provisioning and revocation when a team member leaves.
Prerequisites, with numbers
The official Authentik documentation asks for at least 2 CPU cores and 2 GB of RAM, with Docker Compose v2 (or Podman). On a real VPS we recommend 2 vCPUs and 4 GB of RAM: the server, the worker and PostgreSQL share the same machine, and 2 GB leaves no margin as soon as a connection spike or an outpost is added. Also plan for about twenty gigabytes of free disk for images, the database and a few local backups (an indicative estimate, not an official figure). Authentik is a guide provided on a VPS template: ServOrbit does not support it, you keep its administration.
Checklist before the first docker compose up
- A 64-bit Linux VPS with Docker Engine and the Compose v2 plugin: 2 vCPUs and 4 GB of RAM recommended, 2 cores and 2 GB as the strict official minimum.
- A dedicated subdomain, for example
auth.example.com, with an A record (and AAAA if you have IPv6) pointing to the VPS IP. - Ports 80 and 443 open to the reverse proxy; ports 9000 and 9443 of the server container never exposed publicly.
- A reverse proxy (nginx, Caddy or Traefik) able to obtain a Let's Encrypt certificate.
- A way to recover the administrator account if you lose your second factor: backup codes stored in a password manager.
- A backup location outside the VPS.
Deploy Authentik with Docker Compose
Create the folder and fetch the official compose file
Create a dedicated folder (
mkdir -p /opt/authentik && cd /opt/authentik) then download the reference file withwget https://docs.goauthentik.io/compose.yml. The current file declares three services:postgresql,serverandworker. It references the version available at download time, so you must download it again at each upgrade.Generate the secrets in the .env file
Run
echo "PG_PASS=$(openssl rand -base64 36 | tr -d '\n')" >> .envthenecho "AUTHENTIK_SECRET_KEY=$(openssl rand -base64 60 | tr -d '\n')" >> .env. The documentation states that PostgreSQL passwords longer than 99 characters are not supported. Protect the file (chmod 600 .env), never commit it and never reuse an example key.Bind the ports to localhost only
By default the compose file publishes 9000 (HTTP) and 9443 (HTTPS) on all interfaces. Add
COMPOSE_PORT_HTTP=127.0.0.1:9000andCOMPOSE_PORT_HTTPS=127.0.0.1:9443to.env: only your local reverse proxy can reach them, and public traffic necessarily goes through HTTPS.Start the stack
Run
docker compose pullthendocker compose up -d. Check withdocker compose psthat the three containers are up and thatpostgresqlishealthy, then follow initialization withdocker compose logs -f server. Database migrations can take a few minutes on first launch.Finish the install through /if/flow/initial-setup/
On first launch, open
https://auth.example.com/if/flow/initial-setup/to set the password of theakadminaccount. It is your super-administrator: protect it with MFA immediately, then create a named administrator account for daily use.Create a provider and an application
In the admin, create a Provider (OIDC for a modern app, or Proxy for forward auth) then the matching Application. For OIDC, grab the
client_id/client_secretand the.well-known/openid-configurationURL to paste into the client app.
Reverse proxy and TLS
Authentik must be served over HTTPS for session cookies and WebAuthn to work. With nginx, the auth.example.com vhost does a proxy_pass http://127.0.0.1:9000; and forwards the Host, X-Forwarded-For and X-Forwarded-Proto headers, plus Upgrade and Connection for WebSockets. The certificate comes from certbot --nginx -d auth.example.com; Caddy and Traefik do it automatically. If the domain goes through Cloudflare in proxied mode, set the zone's SSL/TLS mode to Full (strict): in Flexible, nginx redirects HTTP to HTTPS and you get an infinite redirect loop.
Protect an application with forward auth on nginx
Create the Proxy Provider
Create a Proxy Provider in single-application forward auth mode, with the app's public URL (
https://app.example.com) as External host. Attach it to an Application, then to the embedded outpost in the Outposts section.Wire auth_request into the app's vhost
In
location /, addauth_request /outpost.goauthentik.io/auth/nginx;, thenerror_page 401 = @goauthentik_proxy_signin;. Also copy the outpost cookies withauth_request_set $auth_cookie $upstream_http_set_cookie;andadd_header Set-Cookie $auth_cookie;. Copy the full block from the nginx page of the documentation matching your version.Expose the outpost path without authentication
Add a
location /outpost.goauthentik.iothat does aproxy_passtohttp://127.0.0.1:9000, withHostset to the original host,X-Original-URLset to the full URL,proxy_pass_request_body off;and an emptyContent-Length. This path must stay reachable without login, otherwise the redirect never works.Handle the redirect and large headers
Declare a
location @goauthentik_proxy_signinmarkedinternalthat returns a 302 to/outpost.goauthentik.io/start?rd=followed by the original URL. If you see the "upstream sent too big header" error, addproxy_buffers 8 16k;andproxy_buffer_size 32k;. Identities then reach the app in theX-authentik-username,X-authentik-groupsandX-authentik-emailheaders.
Back up and restore Authentik
The PostgreSQL database is the most important part of a backup: it holds users, policies, flows and configuration. With the compose defaults (user and database authentik), a dump is made with docker compose exec -T postgresql pg_dump -U authentik -d authentik -Fc > /var/backups/authentik-$(date +%F).dump. To restore, first stop the application containers with docker compose stop server worker, then run docker compose exec -T postgresql pg_restore -U authentik -d authentik --clean --if-exists < file.dump, and restart. The order recommended by the documentation is: database first, verification, static directories, then start. A backup that was never restored is only a hypothesis: test on a throwaway machine.
What to back up
- The PostgreSQL database: essential, without it Authentik cannot be brought back.
- The
./datadirectory (icons, flow backgrounds, uploaded files, CSV reports), unless you use S3 storage. - The
./certsdirectory if you rely on filesystem certificates; certificates already imported live in the database. - The
./custom-templatesdirectory if you changed the look and feel, and custom blueprints if any. - The
.envfile (secret key and PostgreSQL password), encrypted and kept outside the VPS. - A copy of the dumps on remote storage, never only on the machine hosting the database.
Upgrade without breaking SSO
Authentik does not support downgrading: back up the database before each upgrade and read the release notes of the target. Upgrade sequentially, going through the latest patch of each major.minor version without skipping any. Download the compose file again (wget -O compose.yml https://docs.goauthentik.io/compose.yml), then docker compose pull and docker compose up -d. Upgrade all outposts at the same time: their version must match the server's. Then check the version under Dashboards > Overview; if it did not change and the logs contain migration inconsistency, restore the backup and move up version by version.
Redis is no longer part of the stack
Many older tutorials, including the first version of this article, describe a four-container stack with Redis. The 2025.10 release notes state that Authentik no longer uses Redis at all: cache, embedded outpost and WebSockets moved to PostgreSQL, tasks having already moved in 2025.8. The current compose file therefore contains only postgresql, server and worker. Two consequences: expect roughly 50% more PostgreSQL connections, and if your PostgreSQL requires TLS, Authentik requires TLS 1.3 or the Extended Master Secret extension. Remove any Redis settings left in your configuration.
Troubleshooting: restart loop with AUTHENTIK_POSTGRESQL__USE_POOL
Symptom reported in goauthentik/authentik issue 14320, opened in May 2025 on version 2025.4.0: with AUTHENTIK_POSTGRESQL__USE_POOL=true, the server restarts in a loop, with SSL errors such as "decryption failed or bad record mac" or "SSL SYSCALL error: EOF detected", then a gunicorn worker exiting with code 3 ("Worker failed to boot"). Release 2025.4.1, published in May 2025, disabled the option as a precaution, since the root cause had not been found. Actual state of the issue as read on October 30, 2026: still open, labeled confirmed bug, with no linked fix PR, and a 2026.11.0 milestone marked Required since October 23, 2026. A maintainer's hypothesis: the async SSL context shared across threads, although it is not thread-safe. Reported, unofficial lead: a user stated in September 2026 that AUTHENTIK_WEB__WORKERS=1 and AUTHENTIK_WEB__THREADS=1 removed the problem; in a maintainer's tests, 1 worker and 1 thread are fine, while 2 or more workers reproduce it. This is neither a fix nor a guarantee, and it limits the interface's capacity. The safest course is not to enable the pool (remove the variable from .env) and to reread the issue before any upgrade.
Other common failures
- Redirect loop behind Cloudflare: the zone's SSL/TLS mode is Flexible; switch to Full (strict) and check that
X-Forwarded-Protois forwarded. - WebAuthn or cookies refused: the interface is served over HTTP or under another hostname; enforce HTTPS on Authentik's exact domain.
- 502 error on the protected app: look for "upstream sent too big header" in the nginx logs and increase
proxy_buffer_size. serverandworkerdo not start:postgresqlis nothealthy; readdocker compose logs postgresql, and remember thatPG_PASSandAUTHENTIK_SECRET_KEYare mandatory.- Outpost unreachable after an upgrade: its version no longer matches the server's; upgrade it.
- Containers killed without a message: on 2 GB of RAM the OOM killer strikes; check with
dmesg | grep -i oomand move to 4 GB.
Enable an embedded Outpost and use "property mappings" to inject headers (X-authentik-username, X-authentik-groups) into your forward-auth applications: many apps (Grafana, for example) can read these headers to auto-provision users and assign roles. You get not just SSO but a fully automated group-to-role mapping, with no manual configuration in each tool.