All posts

Authentik on a VPS: one login for the stack

Authentik 2026.8.5: 9000 only on 127.0.0.1, generated secrets, bootstrap admin, one OAuth2 Application, Caddy reverse proxy. Idle 965.7 MiB stack.

Written by NodeRings Inc.

NodeRings Inc. writes these notes. The company runs the marketplace and the agent that stays on the provider’s side of the hypervisor.

Authentik 2026.8.5 held 965.7 MiB idle on this lab host after a bootstrap admin and one OAuth2 Application (Lab App). That is the sum of cgroup memory.current for server, worker, and Postgres 16. Each reading is the median of twenty samples taken 30 seconds apart, while the Admin UI was quiet. Server was 503.2 MiB, worker 301.4 MiB, and Postgres 161.1 MiB. Port 9000 listened on 127.0.0.1 only. A curl to the guest's LAN address on 9000 was refused. Once bootstrap had created akadmin, /if/flow/initial-setup/ returned 404.

Authentik is an open-source identity provider: SSO, OAuth2/OIDC, SAML, LDAP, and proxy outpost flows so many self-hosted apps share one login. Flows and stages let you require MFA, email verification, or invitation-only enrollment. On a VPS it replaces “a different password per container UI” with one IdP, at the cost of more RAM than a single app.

The official compose downloads compose.yml for postgresql, server, and worker (ghcr.io/goauthentik/server:2026.8.5) and publishes 9000 and 9443. Competing guides leave those ports on every interface. Generate PG_PASS and AUTHENTIK_SECRET_KEY, bind HTTP to loopback, and set the bootstrap email and password before the first request. Create one Application and keep Caddy as the only public path. The 2026.8 compose dropped Redis.

Readingserverworkerpostgres
cgroup memory.current503.2 MiB301.4 MiB161.1 MiB
docker stats503.2 MiB301.3 MiB78.2 MiB
CPU percent0.460.110.09
Host listener127.0.0.1:9000unpublishedunpublished

Stack sum (cgroup) is 965.7 MiB.

Skip thisWhat this run did
9000:9000 on every interfacePublished 127.0.0.1:9000:9000
Publishing 9443 next to CaddyLeft 9443 unpublished
Empty secrets until "later"openssl rand into mode 600 .env
Manual initial-setup on a public IPAUTHENTIK_BOOTSTRAP_* before first boot
Stopping at an empty IdPOne OAuth2 Provider + Application
Bar chart. Idle cgroup memory for Authentik server 503.2 MiB, worker 301.4 MiB, Postgres 161.1 MiB, sum 965.7 MiB.
Idle after bootstrap admin and one OAuth2 Application. More providers and outposts sit above this.

How do you install Authentik on Docker?

mkdir -p ~/authentik && cd ~/authentik
wget https://docs.goauthentik.io/compose.yml

Patch the server ports so only loopback HTTP is published. Caddy terminates TLS, so leave host 9443 unpublished:

    ports:
      - "127.0.0.1:9000:9000"

Generate secrets and a bootstrap admin:

echo "PG_PASS=$(openssl rand -base64 36 | tr -d '\n')" >> .env
echo "AUTHENTIK_SECRET_KEY=$(openssl rand -base64 60 | tr -d '\n')" >> .env
echo "[email protected]" >> .env
echo "AUTHENTIK_BOOTSTRAP_PASSWORD=YOUR_LONG_PASSWORD" >> .env
echo "AUTHENTIK_TAG=2026.8.5" >> .env
chmod 600 .env

This lab pinned ghcr.io/goauthentik/server:2026.8.5 (registry index digest sha256:602227a86455d6772e3d16b7d852713c4f864f40df5df894abd1285bb53979a5) and postgres:16-alpine@sha256:0cefaabd882e42bcb2a2f3dc56d3ce4770560ba62fd7f197f1a2e008fd5731ab. Log rotation matches the Compose checklist.

docker compose pull
docker compose up -d
curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:9000/-/health/live/

You want 200. After bootstrap, /if/flow/initial-setup/ returns 404. Sign in as akadmin with the bootstrap password at /if/flow/default-authentication-flow/, then open /if/admin/.

If you keep the official ./custom-templates:/templates mount, the directory has to be readable by the server user. An empty root-only directory produced PermissionError on /templates/if/error.html in this lab. Drop the mount until you have real templates.

Compose for Authentik 2026.8.5 with 127.0.0.1:9000:9000 and postgres 16.
Loopback HTTP only. Postgres stays on the Compose network.
openssl-generated PG_PASS and AUTHENTIK_SECRET_KEY plus AUTHENTIK_BOOTSTRAP email and password in .env.
Mode 600 .env. Bootstrap creates akadmin on first start.

How do you fine-tune Authentik for a small stack?

Create an OAuth2/OIDC Provider with a confidential client, exact redirect URI, and a signing key, then link an Application (first steps, Vultr’s Authentik OAuth2 guide). Add MFA stages before you put finance or admin tools behind it. Outposts (proxy/LDAP/RADIUS) are separate containers; the stock Compose mounts the Docker socket on the worker for auto-deployed outposts. Prefer a socket proxy or manual outpost Compose if you do not want that mount (install docs). Do not bind-mount host /etc/localtime into Authentik; it breaks OAuth/SAML time checks.

Secrets: rotate AUTHENTIK_SECRET_KEY only with a plan; treat Postgres dumps as credential material. Size the guest for ~1 GB idle for server+worker+db before OS. Back up Postgres on a schedule. Keep the admin interface on HTTPS only (COMPOSE_PORT_HTTP=127.0.0.1:9000).

Why must 9000 stay on loopback?

Authentik is the login for the rest of the stack. Anyone who can reach the Admin UI can mint tokens and change providers.

ss -H -lntup | grep 9000

On this host that printed 127.0.0.1:9000. Curl to 192.168.1.150:9000 failed with connection refused. Leave 9000 closed in the panel firewall. Open 80 and 443 for Caddy. Docker published ports often bypass UFW inside the guest, so the loopback bind is what kept the LAN address quiet. From a laptop, forward the Admin UI over SSH.

ssh -N -L 19000:127.0.0.1:9000 user@YOUR_VPS_IP
ss showing LISTEN on 127.0.0.1:9000 only, and curl to the LAN address refused.
Host view of the publish. 9443 stayed unpublished.

How do you add one Application?

After login, open Applications and create one, or use the API with a session cookie and a CSRF header. This lab created an OAuth2/OpenID Provider named lab-app-provider (client_id lab-app-client) and an Application named Lab App (slug lab-app) pointed at https://app.example.com. Crop client secrets.

Wire Vaultwarden, Grafana, or GitLab when each app is ready. Forward-auth outposts are optional. Official compose mounts docker.sock on the worker for outpost management, and this lab kept that mount. Remove it if you are not deploying outposts from this host.

API create of OAuth2 provider returning 201 and applications list showing Lab App.
One OAuth2 Provider and Application. `client_id` lab-app-client.
Authentik Admin Applications page listing Lab App after login.
Admin UI on loopback. Lab App linked to the OAuth2 provider.

Where does Caddy go?

auth.example.com {
	reverse_proxy 127.0.0.1:9000
}

Once DNS is ready, point the brand's external hostnames and the application launch URLs at that HTTPS name. TLS setup is in the Caddy HTTPS post. Authentik's reverse-proxy notes cover X-Forwarded-* when Caddy terminates TLS.

Caddyfile reverse_proxy to 127.0.0.1:9000 for auth.example.com.
Public HTTPS on 443. Authentik stays on loopback HTTP.

How do you back up Postgres?

docker compose exec -T postgresql \
  pg_dump -U authentik authentik > authentik.sql

On this lab the dump was about 4.4 MB after bootstrap and one Application. Archive ./data too if you store media there. Point Restic at both.

pg_dump of authentik database into authentik.sql at 4.4M.
Live `pg_dump` while the stack runs.

How do you roll Authentik out to more apps?

One Application per app, shared or separate providers as the docs suggest. Start with a low-risk app (Dozzle, Dockge) before the password manager. Document the callback URLs. Test logout and token refresh. Keep a break-glass local admin on apps that support it while SSO is new. Monitor worker queue if enrollments spike.

FAQ

Bootstrap or initial-setup? Bootstrap env vars create akadmin before the UI is reachable. That fits a VPS where 9000 stays off the public interface during first boot.

Worker docker.sock? Official compose mounts it for outpost management. This lab kept it. Remove the mount if you deploy outposts another way.

9443? That port is Authentik's internal HTTPS. With Caddy in front, leave 9443 unpublished.

Authelia or Keycloak? Authelia is lighter for forward-auth only. Keycloak is heavier JVM territory. For OIDC, SAML, and a flows UI on one VPS, use Authentik.

Email? Optional for a lab. Add SMTP when you need recovery and invitations.