Authentik SSO setup with Docker Compose and Traefik
Run Authentik for one login across your apps: see the key env values, akadmin bootstrap, Docker Compose setup, and Traefik forward auth.
One login for every app wey you host
Authentik na self-hosted SSO (single sign-on) server: your users go sign in once, and every app wey dey behind am go accept that session instead of asking for im own password. The installation na official Docker Compose file plus two generated secrets. The part wey need real thinking come after: point reverse proxy go am, then put one existing app behind forward auth.
Authentik dey ship with three services for that Compose file: PostgreSQL database, one server process, and one worker process. The server container still dey run the embedded outpost, wey be the component wey dey answer “this request don sign in?” for every protected app. Version 2026.5 na the current release as of July 2026, and the project dey ask for host wey get at least 2 CPU cores and 2 GB of RAM. Treat that one as the minimum. PostgreSQL and the worker go hold memory after the box don dey up for one day.
Wetín you need before you start
You need Docker Engine with the Compose v2 plugin. You fit confirm am with docker compose version. If e print error instead of a version, install the plugin before you continue. The basics dey covered for running apps with Docker Compose on a VPS. You also need DNS A record wey dey point to the server, auth.example.com for the examples below, because Authentik dey build redirect URLs from the hostname wey browser use.
Run the stack as ordinary user wey dey inside docker group, instead of as root. Membership for that group get the same power as root for the host. So give am to one deploy account only, and nobody else, like the guidance for least-privilege user accounts on a VPS.
Install with the official Compose file
sudo install -d -o "$USER" -g "$USER" /opt/authentik
cd /opt/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 -ddocker compose ps suppose list three containers, with postgresql dey report healthy and server, while worker dey report running. The first start dey run database migrations, so wait one minute before web interface go answer.
Both generated values important, but for different reasons. PG_PASS na the PostgreSQL password, and e get hard limit of 99 characters. AUTHENTIK_SECRET_KEY dey sign sessions and tokens, so if you change am later, e go log every user out and invalidate every API token wey you don issue. Keep .env for mode 600 and keep one copy for safe place, because if you restore database without the secret key wey match am, nobody fit log into that database.
The Compose file dey read both values with the ${PG_PASS:?database password required} form. This means say Compose no go start when the file no dey. If you run docker compose up -d from wrong directory, e go print required variable AUTHENTIK_SECRET_KEY is missing a value: secret key required and stop. That message na path problem, no be config problem.
The environment values wey matter
Everything else go enter the same .env file. Authentik dey map double underscore to nested configuration key, so AUTHENTIK_EMAIL__HOST dey set email.host. Single underscore dem dey ignore without warning. Na this be the most common reason why setting go look like say e no do anything.
AUTHENTIK_BOOTSTRAP_PASSWORDdey set password for the built-inakadminuser for first start, so you no go ever type am for public web form.AUTHENTIK_BOOTSTRAP_EMAILandAUTHENTIK_BOOTSTRAP_TOKENdey set that user's address and API token the same way.COMPOSE_PORT_HTTPandCOMPOSE_PORT_HTTPSdey move the published ports from the default 9000 and 9443.AUTHENTIK_EMAIL__HOST,AUTHENTIK_EMAIL__PORT,AUTHENTIK_EMAIL__USERNAME,AUTHENTIK_EMAIL__PASSWORD,AUTHENTIK_EMAIL__USE_TLSandAUTHENTIK_EMAIL__FROMdey configure outbound mail. Without dem, Authentik go trylocalhostfor port 25, so password-reset mails go end as connection error for worker log.AUTHENTIK_LOG_LEVEL=debugdey turn on the detail wey you need while login flow dey misbehave. Set am back toinfoafterwards.AUTHENTIK_ERROR_REPORTING__ENABLEDnafalseby default. Set am totrueonly if you dey okay with sending crash reports upstream.
These ones na secrets for plain file, so treat the directory the same way you go treat any other credential store. Password manager like a self-hosted Vaultwarden instance better pass for keeping recovery copy than note for your laptop.
First login and the admin account
Open http://SERVER_IP:9000 for browser. Authentik go show im initial setup flow and ask you to set password for the default akadmin user. If you don set AUTHENTIK_BOOTSTRAP_PASSWORD already, that step don complete and e go carry you straight go the login page.
Create normal admin user for yourself under Directory and then Users. Add am to authentik Admins group, then sign in with that account. Leave akadmin as break-glass account with long password wey you store offline. Everyday work with shared built-in account go spoil the audit log, because every event go show akadmin and nothing go show who do am. This matter still apply beyond Authentik: something like self-hosted OneCLI harness wey give each person im own agent go only leave readable trail if the identity wey reach am belong to one person, instead of login wey the whole team dey share.
Put Authentik behind your reverse proxy
To publish port 9000 for internet work, e go work, but you need TLS (transport layer security) and real hostname. If you already dey use setup from Traefik as reverse proxy for multiple Compose apps, join Authentik to the same external proxy network with override file. Create docker-compose.override.yml near compose.yml:
services:
server:
networks:
- default
- proxy
labels:
traefik.enable: "true"
traefik.docker.network: proxy
traefik.http.routers.authentik.rule: Host(`auth.example.com`)
traefik.http.routers.authentik.entrypoints: websecure
traefik.http.routers.authentik.tls.certresolver: le
traefik.http.services.authentik.loadbalancer.server.port: "9000"
networks:
proxy:
external: trueApply am with docker compose up -d. Compose go merge the override automatically, so server service go keep everything from the official file and gain the labels. Check am with curl -I https://auth.example.com/if/user/; e suppose answer HTTP/2 200. If Traefik return 404 page not found, e mean say the container no dey for proxy network, and Traefik no fit route request to container wey e no fit reach.
Once the hostname dey work, bind the published ports to 127.0.0.1 for the override, so na only through the proxy person fit enter.
Protect one app with forward auth
Authentik proxy provider get three modes, and if you choose wrong one, e fit waste one hour. Proxy mean say the outpost itself dey forward traffic go the upstream app. Forward auth (single application) mean say your own reverse proxy still dey move the traffic, and e only ask Authentik whether user don sign in. Forward auth (domain level) dey protect every app under one parent domain with one provider, but you no go get authorization rules for each app. If Traefik dey in front, you need forward auth (single application). If you want one real app to practise with, something like self-hosted AFFiNE workspace na good first candidate, because na the kind internal tool wey you want make your own devices reach, and nobody else. Team tool make the reason stronger: put self-hosted Chatwoot support desk behind the same provider, so everybody wey dey answer the inbox go sign in once for the day instead of sharing another password.
For the web interface, open Applications and then Providers, create a Proxy Provider, choose the forward auth single application mode, and set the external host to https://app.example.com. Create an Application wey points to that provider. Then open Outposts, edit the authentik Embedded Outpost, and move the new application into its selected applications. The outpost only answers for applications wey you give am, so if you skip that last step, correctly configured provider fit still return nothing.
Define the middleware once, for the Authentik container, then reference am from every protected app:
traefik.http.middlewares.authentik.forwardauth.address: http://server:9000/outpost.goauthentik.io/auth/traefik
traefik.http.middlewares.authentik.forwardauth.trustForwardHeader: "true"
traefik.http.middlewares.authentik.forwardauth.authResponseHeaders: X-authentik-username,X-authentik-groups,X-authentik-email,X-authentik-name,X-authentik-uid,X-authentik-jwt,X-authentik-meta-jwks,X-authentik-meta-outpost,X-authentik-meta-provider,X-authentik-meta-app,X-authentik-meta-versionauthResponseHeaders na the list of headers wey Traefik copies from Authentik answer onto the request wey e sends upstream. If you omit am, the app still dey protected, but e no go know who the user be. So anything wey reads X-authentik-username for automatic login go remain logged out. This gap dey obvious especially before an app wey keeps its own sign-in, such as self-hosted openGym workout tracker and its passkey login. The headers na the difference between one prompt and two for the same page.
The protected app itself needs two routers, no be one:
labels:
traefik.enable: "true"
traefik.http.routers.myapp.rule: Host(`app.example.com`)
traefik.http.routers.myapp.entrypoints: websecure
traefik.http.routers.myapp.tls.certresolver: le
traefik.http.routers.myapp.middlewares: authentik@docker
traefik.http.routers.myapp-auth.rule: Host(`app.example.com`) && PathPrefix(`/outpost.goauthentik.io/`)
traefik.http.routers.myapp-auth.entrypoints: websecure
traefik.http.routers.myapp-auth.tls.certresolver: le
traefik.http.routers.myapp-auth.priority: "15"
traefik.http.routers.myapp-auth.service: authentikThe second router na the part wey everybody dey leave out. After sign-in, Authentik sends the browser back to a path under /outpost.goauthentik.io/ for the app's hostname, no be for auth.example.com. If no router sends that path prefix to the Authentik service, the request go reach your app, the app go answer 404, and the login no go finish. The higher priority na wetin make the specific path rule win over the plain Host() rule for the same domain.
Test am inside a private browser window. You suppose go to auth.example.com, sign in, then return to the app. docker compose logs -f server for the Authentik side prints one authorization event for each attempt. This tells you whether the request reach Authentik at all.
Wahala wey you go really meet
Redirect loop wey no dey end between the app and the login page. The external host for the provider no match wetin browser dey use, usually http:// for the provider against https:// for the address bar. The session cookie go then set for another origin, so every time request return, e go look like fresh anonymous request. Correct the external host and clear cookies for both domains before you test again.
404 for /outpost.goauthentik.io/start. The outpost router no dey there, or its priority lower than the catch-all router for that host.
The app loads but e no ever ask for login. The middlewares label dey name middleware wey no exist. Traefik no dey warn about that, so typo for authentik@docker simply mean say no middleware dey run. Open the Traefik dashboard and confirm say the router list the middleware.
403 from Authentik after login succeed. The user don authenticate but e no get authorization: the application get policy binding, or group requirement, wey this user no satisfy. The Events log for the admin interface go show the policy wey deny am.
When Keycloak dey make more sense
Keycloak na the older project, and Red Hat dey support am. E stronger for classic enterprise identity work: heavy SAML federation, brokering login from several external identity providers at the same time, and realm export and import as documented migration path. Commercial support behind am fit matter to some organisations for paper. The trade-off be say Keycloak no get proxy of im own. So, to protect app wey no speak OIDC (OpenID Connect), you need run something like oauth2-proxy beside am. Authentik built-in proxy provider already get this part integrated. Na why most self-hosters wey get different kinds of apps dey choose Authentik.
Backups and upgrades
Three things dey make restore possible: the PostgreSQL database, the ./data directory, and .env.
cd /opt/authentik
docker compose exec -T postgresql pg_dump -U authentik authentik | gzip > authentik-$(date +%F).sql.gzStore that dump and .env together. The dump alone no enough, because the secret key wey dey protect session and token data dey inside .env.
Upgrades na tag change. Set AUTHENTIK_TAG inside .env to the release wey you want, then run docker compose pull followed by docker compose up -d. Read the release notes first, because Authentik dey use date-based versions and some releases get migrations wey expect say you dey come from the previous one. Take the database dump before the pull, no be after.
FAQ
Authentik free to self-host?
The open source edition free, and e cover everything we talk about above: the proxy provider, forward auth, OIDC (OpenID Connect), SAML, and the flows engine. Paid enterprise tier add support and some enterprise features, but you no need licence for anything here.
I need Traefik before I fit use Authentik?
No. Forward auth dey work with nginx through auth_request and with Caddy through forward_auth. The pattern dey the same for every case: reverse proxy go ask Authentik about each request, and path prefix /outpost.goauthentik.io/ for the protected hostname must route go Authentik instead of the app.
Why my protected app dey move between login and error forever?
The external host wey you configure for the proxy provider no match the URL wey browser dey use, most times http against https. Session cookie dey issue for one origin and browser dey read am for another, so Authentik dey see anonymous request every time. Correct the external host, then clear cookies for both hostnames before you test again.
How much RAM Authentik need?
The documented minimum na 2 CPU cores and 2 GB of RAM as of July 2026. This one cover PostgreSQL, the server, and the worker together. For 2 GB box, worker na the first process wey kernel go kill when memory pressure happen. The symptom na background tasks and outbound email stop, while login page still dey work. Give am 4 GB if the same server dey run the apps wey you dey protect.