Authentik
We document the setup of Authentik, the identity provider
used to protect the private services at piribauer.ch.
Authentik runs in Docker and exposes its server on
127.0.0.1:9000. All protected apps delegate
authentication to it via nginx forward-auth — they never implement
any login logic themselves.
We assume Docker and nginx are already installed, and that a wildcard or per-subdomain TLS certificate is in place (we use Certbot).
Docker Compose
We create the working directory and place the compose file there:
$ mkdir -p /home/julian/authentik
$ cd /home/julian/authentik
The docker-compose.yml runs four services:
PostgreSQL, Redis, and the Authentik server and worker. The server
is bound to 127.0.0.1:9000 so it is only reachable
locally.
services:
postgresql:
image: docker.io/library/postgres:16-alpine
restart: unless-stopped
volumes:
- database:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: ${PG_PASS}
POSTGRES_USER: ${PG_USER:-authentik}
POSTGRES_DB: ${PG_DB:-authentik}
redis:
image: docker.io/library/redis:alpine
command: --save 60 1 --loglevel warning
restart: unless-stopped
volumes:
- redis:/data
server:
image: ghcr.io/goauthentik/server:2024.12.3
restart: unless-stopped
command: server
environment:
AUTHENTIK_REDIS__HOST: redis
AUTHENTIK_POSTGRESQL__HOST: postgresql
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
AUTHENTIK_ERROR_REPORTING__ENABLED: "false"
volumes:
- ./media:/media
- ./custom-templates:/templates
ports:
- "127.0.0.1:9000:9000"
- "127.0.0.1:9443:9443"
worker:
image: ghcr.io/goauthentik/server:2024.12.3
restart: unless-stopped
command: worker
user: root
environment:
AUTHENTIK_REDIS__HOST: redis
AUTHENTIK_POSTGRESQL__HOST: postgresql
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
AUTHENTIK_ERROR_REPORTING__ENABLED: "false"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./media:/media
- ./certs:/certs
- ./custom-templates:/templates
volumes:
database:
driver: local
redis:
driver: local
Environment file
We create /home/julian/authentik/.env with the
database credentials and a secret key. The secret key should be a
long random string — it is used to sign sessions and must not change
after the first start.
PG_PASS=<random password>
PG_USER=authentik
PG_DB=authentik
AUTHENTIK_SECRET_KEY=<long random string>
AUTHENTIK_ERROR_REPORTING__ENABLED=false
Starting Authentik
$ cd /home/julian/authentik
$ docker compose up -d
Since docker.service is enabled in systemd and all containers
have restart: unless-stopped, Authentik comes back up
automatically after a reboot.
On first start, we navigate to
https://auth.piribauer.ch/if/flow/initial-setup/ to set
the admin password.
Nginx — Authentik itself
We proxy auth.piribauer.ch to the local server:
server {
server_name auth.piribauer.ch;
location / {
proxy_pass http://127.0.0.1:9000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
}
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/piribauer.ch/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/piribauer.ch/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}
Creating a protected application
For each service we want to protect, we create an application and
a proxy provider in the Authentik admin UI at
https://auth.piribauer.ch/if/admin/.
Under Providers, we create a new Proxy
Provider in forward-auth mode for the application’s domain
(e.g. https://hub.piribauer.ch). We then create an
Application linked to that provider. Finally, under
Outposts, we add the application to the embedded
outpost so the outpost begins serving the forward-auth endpoint for
it.
Nginx — forward-auth for protected apps
Each protected app follows the same nginx pattern. We add two
locations and an auth_request directive to the main
location:
# Authentik outpost endpoint
location /outpost.goauthentik.io {
proxy_pass http://127.0.0.1:9000/outpost.goauthentik.io;
proxy_set_header Host $host;
proxy_set_header X-Original-URL $scheme://$host$request_uri;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
add_header Set-Cookie $auth_cookie;
auth_request_set $auth_cookie $upstream_http_set_cookie;
}
# Redirect unauthenticated requests to the login page
location @goauthentik_proxy_signin {
internal;
add_header Set-Cookie $auth_cookie;
return 302 /outpost.goauthentik.io/start?rd=$scheme://$http_host$request_uri;
}
location / {
# ... proxy_pass or root directive ...
auth_request /outpost.goauthentik.io/auth/nginx;
error_page 401 = @goauthentik_proxy_signin;
auth_request_set $auth_cookie $upstream_http_set_cookie;
add_header Set-Cookie $auth_cookie;
# Optional: expose user attributes to the backend
auth_request_set $authentik_username $upstream_http_x_authentik_username;
auth_request_set $authentik_email $upstream_http_x_authentik_email;
proxy_set_header X-Authentik-Username $authentik_username;
proxy_set_header X-Authentik-Email $authentik_email;
}
On each request, nginx calls the outpost’s
/auth/nginx endpoint. If the user has a valid session,
the outpost returns 200 and nginx proceeds, injecting the username
and email as headers. If not, nginx returns 401 which triggers the
redirect to the login flow.