Skip to content

How to Deploy authentik on a VPS

Updated Aug 2026

Self-host authentik on a VPS — an open-source SSO identity provider with SAML, OAuth2/OIDC and LDAP, deployed with Docker Compose, real secrets and HTTPS.

Before you start
  • A VPS with at least 2 GB RAM and 2 CPU cores — this is a real floor, not a suggestion
  • A fresh Ubuntu 24.04 or 26.04 server with root/sudo SSH access
  • A domain you can point at the server — an identity provider must be served over HTTPS
  • Docker Engine + Compose installed (see the base guide below)

What authentik is

authentik is a self-hosted identity provider: one login for every application you run. It speaks SAML, OAuth2/OIDC, and LDAP, offers proxy and forward-auth for apps that have no login of their own, and builds its login journeys out of a visual flow editor — so "password, then TOTP, unless the user is in this group" is something you assemble rather than code. It's MIT-licensed, built from Python, Go, and TypeScript, rated 3 / 5 to deploy, and wants 2 GB of RAM as a starting point.

That last number is the honest headline. Most apps on this site are one container that idles in a few hundred megabytes. authentik is a multi-service stack — a Postgres database, an application server, and a background worker — and the upstream requirement is genuinely a host with at least 2 CPU cores and 2 GB of RAM. It will start on less and then behave badly under load, so size for it up front. In exchange you stop managing a separate account list in every app you host, which is worth the box.

Server sizing — plan for a stack, not a container

The three services split the load: Postgres holds users, groups, applications, and the flow definitions; the server handles logins and the admin interface; the worker runs background jobs — outposts, directory syncs, certificate renewals, and scheduled tasks.

  • 2 GB RAM / 2 vCPU — the documented floor and a workable personal or homelab instance. Expect the box to be busy during a restart.
  • 4 GB RAM / 2 vCPU — the setting to actually choose for anything a team logs into daily. Migrations, directory syncs, and a handful of concurrent logins all have room.
  • 8 GB RAM+ — larger user directories, LDAP synchronization against an existing source, or several proxy outposts on the same host.

Disk is small — 20–40 GB covers the images, the database, and uploaded branding assets. The reason to buy headroom here isn't capacity, it's availability: when this box is down, nothing that trusts it can be logged into. A Hetzner CX22 (2 vCPU / 4 GB) is the natural fit; Kamatera lets you size CPU and RAM independently if you'd rather tune it.

Prepare the server

This guide assumes Docker Engine and the Compose plugin are installed, along with a non-root deploy user and a ufw firewall. If not, start with Docker & Compose on Ubuntu.

Open SSH plus the reverse proxy ports and nothing else — authentik's own ports stay on loopback:

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

Create the project directory:

mkdir ~/authentik && cd ~/authentik

Install authentik

authentik ships an official Compose file, and the install is that file plus two generated secrets. Do not invent these by hand — they're a database password and an application secret key, and both want real entropy.

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

Understand what those two lines just created, because they are the parts you cannot regenerate later:

  • PG_PASS is the Postgres password. The database is created with it on first run; changing it afterwards means changing it in Postgres too.
  • AUTHENTIK_SECRET_KEY signs sessions and encrypts stored secrets — API tokens, provider client secrets, source credentials. Lose this key and the encrypted values in your database become unreadable, even with a perfect database backup. Treat .env as the most sensitive file on the box.

Next, keep the web ports off the public interface. The compose file publishes its HTTP port from a variable, so set that variable rather than editing the file — your change then survives the next compose download at upgrade time:

echo "COMPOSE_PORT_HTTP=127.0.0.1:9000" >> .env
echo "COMPOSE_PORT_HTTPS=127.0.0.1:9443" >> .env

Lock the file down and start the stack:

chmod 600 .env
docker compose pull
docker compose up -d
docker compose logs -f

First startup is slow — the database is initialized and a long list of migrations runs. Two or three minutes on a small box is normal. Wait for the server to report that it's listening before you go looking for the web UI.

HTTPS + domain

An identity provider handles passwords and issues tokens for every application behind it. Plain HTTP is not an option, and several of the protocols involved (WebAuthn in particular) simply refuse to work without a secure context.

Create an A record for auth.example.com pointing at the server's public IP, wait for it to resolve, then terminate TLS in front of the HTTP port. The simplest path is Automatic HTTPS with Caddy:

auth.example.com {
    reverse_proxy 127.0.0.1:9000
}

Proxy to the HTTP port (9000), not the HTTPS one — Caddy is already terminating TLS, and pointing it at 9443 means two layers of TLS and a confusing certificate error. If you run Caddy as a container, 127.0.0.1 is the proxy's own loopback: put both in one compose project and use reverse_proxy server:9000 against the authentik service name instead.

Once it's live, tell authentik its public URL. The initial setup flow works either way, but tokens, redirects, and email links are generated from what the proxy forwards, so make sure your proxy passes the original host header — Caddy's reverse_proxy does this by default.

First login and hardening

The initial administrator is created through a one-time setup flow. Open:

https://auth.example.com/if/flow/initial-setup/

That page sets the email and password for the built-in akadmin account. It's a one-time flow — once the password is set, the URL stops being useful, which is exactly why you should visit it immediately after the stack comes up rather than leaving it for the morning.

Then harden, in this order:

  1. Set a long, unique password for akadmin. This account can impersonate users and mint tokens for every connected application.
  2. Enrol a second factor right away. authentik supports TOTP and WebAuthn; add one under the user settings, then require MFA in your authentication flow so it applies to everyone, not just you.
  3. Create a normal admin account for day-to-day work and stop using akadmin except for recovery.
  4. Review the default flows. If you don't want strangers creating accounts, confirm no enrolment flow is bound to your authentication stage.
  5. Keep /if/admin/ behind MFA and consider restricting it by source IP at the reverse proxy if your admin access comes from a fixed location.

Connect your first application

An identity provider proves itself the moment a second app uses it. The pattern in authentik is always the same pair: a provider (the protocol side — OAuth2/ OIDC, SAML, LDAP, or Proxy) and an application (the entry users see), linked together and then scoped with a policy or group binding.

For a typical OIDC app:

  1. Create an OAuth2/OpenID Provider, set the redirect URI your app expects, and copy the generated client ID and secret.
  2. Create an Application bound to that provider, with a slug and a name.
  3. Paste the issuer URL, client ID, and secret into the app's OIDC settings — the issuer is your authentik URL plus the application slug path shown on the provider page.
  4. Bind a group to the application so only the right people see it.

For apps with no login support at all, use a Proxy Provider with an outpost in front of them — the reverse proxy asks authentik whether the request is authenticated before it ever reaches the app.

Backups

Three things must be backed up together. Any one missing makes the restore incomplete:

  • The Postgres database — users, groups, applications, providers, flows, and every encrypted secret.
  • .env — specifically AUTHENTIK_SECRET_KEY and PG_PASS. Without the secret key, the encrypted columns in that database are garbage.
  • The ./data, ./certs, and ./custom-templates directories — media, branding assets, certificates, and any templates you customized.

Dump the database from the running container:

cd ~/authentik
docker compose exec -T postgresql \
  pg_dump -U authentik authentik | gzip > authentik-db-$(date +%F).sql.gz

Then archive the rest and encrypt the bundle before it leaves the box — it contains the key that unlocks everything else:

tar czf - authentik-db-$(date +%F).sql.gz .env data certs custom-templates \
  | gpg --symmetric --cipher-algo AES256 \
      -o "authentik-$(date +%F).tar.gz.gpg"

Copy the .gpg file off the server, schedule it, and restore it once into a throwaway instance. An identity provider is a single point of failure by design — every app behind it is locked out while it's down, so "how fast can I rebuild this" is a question worth having already answered.

Upgrades

authentik pins its version inside the Compose file, so an upgrade means taking the newer Compose file and pulling:

cd ~/authentik
docker compose exec -T postgresql pg_dump -U authentik authentik | gzip > pre-upgrade.sql.gz
wget -O compose.yml https://docs.goauthentik.io/compose.yml
docker compose pull
docker compose up -d

Back up before every upgrade — database migrations run automatically at startup and are one-way. Read the release notes before jumping across several releases; authentik moves quickly and occasionally changes flow or provider defaults. Because the port publish came from .env, re-downloading the Compose file doesn't undo your loopback binding.

Troubleshooting

/if/flow/initial-setup/ returns "not found" or a policy denial. Either the admin password is already set (the flow is one-time), or the stack hadn't finished migrating when you loaded it. Restart the containers and retry; if the account really is configured and you've lost access, generate a recovery link from the host:

docker compose run --rm server create_recovery_key 10 akadmin

That prints a URL valid for ten minutes that logs you in as akadmin.

The stack starts and then the server restarts in a loop. Check docker compose logs server. The two usual causes are a missing or malformed .env (the Compose file hard-fails when PG_PASS or AUTHENTIK_SECRET_KEY is absent) and the database not being ready yet on a slow, memory-starved box.

Everything is slow, or the worker never finishes tasks. This is the RAM floor asserting itself. Check docker stats and free -m; if the box is swapping, the fix is a bigger instance, not a configuration tweak.

Login works over the IP but fails from the domain, or redirects loop. The reverse proxy isn't forwarding the original host, so authentik generates redirect URLs for the wrong origin. Confirm the proxy passes Host and X-Forwarded-* headers, and that you're proxying to port 9000 rather than double-terminating TLS on 9443.

After a restore, users can't log in and provider secrets are invalid. The AUTHENTIK_SECRET_KEY from the original .env didn't come back. The database alone is not a restore — recover the key or re-create the affected providers and tokens from scratch.

Verification + next steps

You're done when you can: load https://auth.example.com over a valid certificate, complete the initial setup flow, log in as an admin with a second factor enrolled, create a provider and application, log into a real app through authentik, and produce an encrypted backup — database, .env, and data directories — that you have restored at least once.

From there, put a Proxy Provider in front of the services that never had logins, sync an existing directory as a source, and standardize your other deploys on the same login. If you're still choosing an identity provider, Authelia vs authentik covers whether you need a full IdP at all, and authentik vs Keycloak covers the trade-off between the two big ones. For the ranked host picks under a stack like this, see Best VPS for Self-Hosting.

Next steps

Search SelfHost Atlas

Search apps, comparisons, guides, and categories.

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