Skip to content

How to Deploy authentik on a VPS

Updated Sep 2026

We earn commissions when you shop through the links below. Full disclosure →

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)
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 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 2 vCPU / 4 GB VPS is the natural fit.

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
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 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

How to self-host authentik →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 Authelia 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.