# authentik — User Guide

**authentik** is an open-source Identity Provider (IdP) and Single Sign-On (SSO) platform built for modern, protocol-diverse environments. It speaks **SAML**, **OAuth2 / OIDC**, **LDAP**, **RADIUS**, **SCIM**, **WS-Federation**, **Microsoft Entra**, **Google Workspace**, and **SSF** (Shared Signals Framework) — out of one server. You self-host it, configure it once, then point every app you want to secure at it.

This guide covers everything you need to install, configure, and operate authentik day-to-day. It is written against version `2026.8.0-rc1` (commit `9e75f62`). Where the upstream documentation at [docs.goauthentik.io](https://docs.goauthentik.io) goes deeper on a particular integration, this guide links there.

> [!IMPORTANT]
> authentik has a **dual license model**. The core (MIT, in `authentik/`) is forever-free. The **Enterprise** tier (`authentik/enterprise/` under a custom EE License) adds features like SSF, MS Entra/Google Workspace sync, audit logging, MTLS stage, RAC, account lockdown, and unique-password policy. The two are in the same container — your license key activates Enterprise modules at runtime.

---

## Table of Contents

1. [What authentik Does](#1-what-authentik-does)
2. [Concepts You Must Know](#2-concepts-you-must-know)
3. [Quick Start (Docker Compose)](#3-quick-start-docker-compose)
4. [Installation](#4-installation)
5. [Initial Setup](#5-initial-setup)
6. [Adding Your First Application](#6-adding-your-first-application)
7. [Flows and Stages](#7-flows-and-stages)
8. [Providers — Securing Apps](#8-providers--securing-apps)
9. [Sources — Authenticating Users](#9-sources--authenticating-users)
10. [Outposts](#10-outposts)
11. [Users, Groups, and RBAC](#11-users-groups-and-rbac)
12. [Policies and Property Mappings](#12-policies-and-property-mappings)
13. [Blueprints — GitOps for authentik](#13-blueprints--gitops-for-authentik)
14. [Customization and Branding](#14-customization-and-branding)
15. [Events and Notifications](#15-events-and-notifications)
16. [Backups, Upgrades, and Air-Gapped Installs](#16-backups-upgrades-and-air-gapped-installs)
17. [Common Tasks (Recipes)](#17-common-tasks-recipes)
18. [CLI Reference (`ak`)](#18-cli-reference-ak)
19. [Troubleshooting](#19-troubleshooting)
20. [FAQ](#20-faq)
21. [Reference Tables](#21-reference-tables)

---

## 1. What authentik Does

authentik sits between your users and the apps you want to secure. A user opens an app. The app hands them off to authentik. authentik proves who they are using whatever combination of password / MFA / federated login / certificate / device check you've configured. authentik hands them back to the app with a signed token (SAML assertion, OIDC ID token, etc.) that the app trusts.

### Capabilities

- **Identity Provider** — authentik is the source of truth for who your users are. Local users, federated users, or both.
- **Single Sign-On** — sign in once at authentik, access dozens of apps without re-entering credentials.
- **Multi-protocol** — SAML, OAuth2/OIDC, LDAP, RADIUS, SCIM, WS-Federation, Forward Auth (Proxy provider), Remote Access Connection (RAC, an in-browser RDP/SSH/VNC gateway).
- **Inbound federation** — accept logins from external LDAP, OAuth, SAML, Kerberos, SCIM, Plex, Telegram.
- **Outbound sync** — push users and groups to MS Entra ID, Google Workspace, or any SCIM endpoint.
- **Flexible MFA** — TOTP, WebAuthn (passkeys, security keys), Duo, SMS, email, static codes, endpoint device attestation (GDTC).
- **Self-service** — users can recover passwords, manage MFA devices, view assigned apps, edit profile fields — without admin involvement.
- **Audit trail** — every login, every config change, every consent grant is logged. Forwardable to Sentry, syslog, or your SIEM.
- **GitOps-native** — every object (Flow, Stage, Policy, Provider, Application, User, Group) can be expressed as a YAML **blueprint** and applied declaratively.

### What authentik is NOT

- Not a password manager. Users still need to remember their authentik credentials (or federate from one they already do).
- Not a CASB or proxy gateway *by itself* — but the **Proxy provider** + an outpost gives you Forward Auth (à la Authelia / oauth2-proxy) for any app that supports header-based auth or Traefik/Nginx forward-auth.
- Not an MDM. authentik integrates with endpoint-attestation systems but doesn't manage devices.

---

## 2. Concepts You Must Know

These six concepts run through every page of the admin UI. Learn them first.

| Concept | What it is | Lives at |
|---|---|---|
| **Flow** | An ordered sequence of stages that completes one user-facing journey (login, recovery, enrollment, invalidation). | Admin → Flows and Stages → Flows |
| **Stage** | A single step inside a flow (identification, password, MFA, consent, user-write, etc.). 24 stage types ship in core + EE. | Admin → Flows and Stages → Stages |
| **Policy** | A boolean expression (Python or rule-based) that gates whether a stage executes, an app is visible, or a flow advances. | Admin → Customization → Policies |
| **Provider** | Outbound protocol implementation — SAML, OAuth2, LDAP, RADIUS, Proxy, RAC, SCIM, etc. The thing apps talk to. | Admin → Applications → Providers |
| **Source** | Inbound protocol implementation — federated login from external IdPs. | Admin → Directory → Federation & Social login |
| **Application** | A user-visible "tile" in the User interface. Wraps one Provider and (optionally) some policies. | Admin → Applications → Applications |

A simple login flow looks like this:

```text
User → Application "Slack" → Provider (SAML) → authentik
   ↓
Flow "default-authentication-flow":
   Stage 1: Identification (enter username)
   Stage 2: Password
   Stage 3: Authenticator Validation (MFA)
   Stage 4: User Login (issue session)
   ↓
SAML assertion → Slack → user is signed in
```

> [!TIP]
> If you only remember one thing, remember this: **Flows are recipes. Stages are ingredients. Policies decide which ingredients to use today.**

---

## 3. Quick Start (Docker Compose)

The fastest path to a working authentik. Requires 2 CPU cores, 2 GB RAM, and Docker Compose v2.

```bash
mkdir authentik && cd 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 -d
```

Then open `http://<your-server>:9000/if/flow/initial-setup/` and set the password for the `akadmin` user.

Expected output of `docker compose up -d`:

```text
[+] Running 5/5
 ✔ Network authentik_default       Created
 ✔ Container authentik-redis-1     Started   (NOTE: only present in legacy compose files; current version uses PostgreSQL listen/notify, no Redis)
 ✔ Container authentik-postgresql  Started
 ✔ Container authentik-server      Started
 ✔ Container authentik-worker      Started
```

> [!IMPORTANT]
> **PostgreSQL passwords must be ≤ 99 characters** due to a PostgreSQL libpq limitation. The `openssl rand -base64 36` above produces a 48-char password, which is well within the limit. If you choose a custom password, stay under 100 chars.

> [!CAUTION]
> All authentik containers must run in **UTC**. Do *not* mount `/etc/timezone` or `/etc/localtime` — it breaks OAuth and SAML clock-skew validation. Times in the UI are localized client-side. (See [GitHub #3005](https://github.com/goauthentik/authentik/issues/3005).)

---

## 4. Installation

### Choose your deployment

=== "Docker Compose"

    Best for: lab, homelab, small production (< 500 users, single host).

    ```bash
    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 -d
    ```

    The compose file pins a specific authentik version. To upgrade, re-download the file (it tracks the latest stable release) and run `docker compose pull && docker compose up -d`.

=== "Kubernetes (Helm)"

    Best for: production at scale, HA, multi-replica.

    ```bash
    helm repo add authentik https://charts.goauthentik.io
    helm repo update
    helm upgrade --install authentik authentik/authentik \
      -n authentik --create-namespace \
      --set authentik.secret_key=$(openssl rand -base64 60 | tr -d '\n') \
      --set postgresql.auth.password=$(openssl rand -base64 36 | tr -d '\n') \
      --set postgresql.enabled=true
    ```

    The chart's full schema lives at [github.com/goauthentik/helm](https://github.com/goauthentik/helm). Override `values.yaml` for production: external PostgreSQL, replicaCount > 1 for server and worker, ingress with cert-manager, and `geoip.enabled=true` if you want GeoIP-based policies.

=== "AWS CloudFormation"

    Best for: AWS shops that want a one-click deploy.

    Templates are hosted at [docs.goauthentik.io/docs/install-config/install/aws](https://docs.goauthentik.io/docs/install-config/install/aws/). The bundled CDK app lives at `lifecycle/aws/app.py` in the repo.

=== "DigitalOcean Marketplace"

    Best for: trying authentik on a fresh droplet.

    [DigitalOcean Marketplace listing](https://marketplace.digitalocean.com/apps/authentik) provisions a droplet with authentik pre-installed via Docker Compose.

### System requirements

| Profile | CPU | RAM | Disk | Postgres |
|---|---|---|---|---|
| Lab / homelab | 2 cores | 2 GB | 10 GB | bundled |
| Small prod (≤ 500 users) | 2 cores | 4 GB | 20 GB | bundled or external |
| Medium prod (≤ 10k users) | 4 cores | 8 GB | 50 GB | external recommended |
| Large prod / HA | 8+ cores per replica × N | 16+ GB per replica | 100+ GB | external + replica |

> [!NOTE]
> authentik **does not require Redis** in current versions. Pub/sub uses PostgreSQL `LISTEN/NOTIFY` via the bundled `django-channels-postgres` and `django-postgres-cache` packages. This simplifies deployment but means PostgreSQL is your only stateful dependency.

### Email (recommended)

Configure SMTP early — authentik uses email for password resets, MFA, invitations, and admin alerts.

```yaml
# in your compose .env or values.yaml
AUTHENTIK_EMAIL__HOST: smtp.example.com
AUTHENTIK_EMAIL__PORT: 587
AUTHENTIK_EMAIL__USERNAME: [email removed]
AUTHENTIK_EMAIL__PASSWORD: <smtp-app-password>
AUTHENTIK_EMAIL__USE_TLS: "true"
AUTHENTIK_EMAIL__FROM: [email removed]
```

Test it after restart:

```bash
docker compose exec worker ak test_email [email removed] -S default-email-stage
```

Expected output:

```text
Sending test email via stage 'default-email-stage' to [email removed]... sent.
```

### Reverse proxy

You almost certainly want one. authentik listens on `:9000` (HTTP) and `:9443` (HTTPS) by default. Place it behind Traefik / Nginx / Caddy / HAProxy with a real TLS cert.

> [!IMPORTANT]
> Add your reverse proxy's IP to `AUTHENTIK_LISTEN__TRUSTED_PROXY_CIDRS` so authentik trusts `X-Forwarded-For`. Defaults cover RFC1918 ranges (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`) and IPv6 link-local — if you're behind a non-RFC1918 reverse proxy (Cloudflare Tunnel, public load balancer), add its CIDRs explicitly.

For full reverse-proxy snippets see [docs.goauthentik.io/docs/install-config/reverse-proxy](https://docs.goauthentik.io/docs/install-config/reverse-proxy).

---

## 5. Initial Setup

1. Open `http://<your-server>:9000/if/flow/initial-setup/`.
2. Set a password for `akadmin` (the bootstrap superuser).
3. You're redirected to the Admin interface at `/if/admin/`.

> [!TIP]
> The User interface (`/if/user/`) is what your end users see. The Admin interface (`/if/admin/`) is the config UI. The Flow interface (`/if/flow/`) is what runs during a login/enroll/recovery. Bookmark whichever you use most.

### Verify the install

In the Admin interface:

1. **Dashboard** — should show "System status: All good", PostgreSQL connected, embedded outpost healthy.
2. **System → Workers** — at least one worker is connected (Last seen < 60s ago). If "No workers" persists, see [Troubleshooting](#19-troubleshooting).
3. **Flows and Stages → Flows** — you should see 6+ default flows: `default-authentication-flow`, `default-invalidation-flow`, `default-password-change-flow`, `default-source-enrollment-flow`, `default-source-authentication-flow`, `default-user-settings-flow`.

If any of the above is missing, the bundled blueprints didn't apply. Run `docker compose exec worker ak apply_blueprint default/0-bootstrap.yaml` and check the System Tasks page for the result.

### First admin actions

1. Replace `akadmin` with a real account: **Directory → Users → Create** (your name) → assign to the `authentik Admins` group → log out, log back in as you.
2. Disable `akadmin`: **Directory → Users → akadmin → Deactivate**. (Don't delete — keep it for break-glass recovery.)
3. Generate a recovery key in case you lock yourself out:

```bash
docker compose exec worker ak create_recovery_key 525600 akadmin
```

The output is a URL like `https://<your-server>/if/flow/...?token=...`. Print it, store it offline. It's valid for 525,600 minutes (one year).

---

## 6. Adding Your First Application

Let's secure a real app — Grafana via OAuth2/OIDC.

### Step 1 — Create the Provider

1. **Admin → Applications → Providers → Create → OAuth2/OpenID Provider**.
2. Name: `Grafana OAuth`.
3. Authorization flow: `default-provider-authorization-implicit-consent` (or `explicit-consent` if you want users to see the scopes screen).
4. Client type: `Confidential`.
5. Note the auto-generated **Client ID** and **Client Secret**.
6. Redirect URIs: `https://grafana.example.com/login/generic_oauth` (exact match — wildcards via regex are supported but discouraged).
7. Signing Key: pick `authentik Self-signed Certificate` (or your own).
8. Save.

### Step 2 — Create the Application

1. **Admin → Applications → Applications → Create**.
2. Name: `Grafana`.
3. Slug: `grafana` (URL-safe, becomes `/application/o/grafana/`).
4. Provider: select `Grafana OAuth`.
5. Launch URL: `https://grafana.example.com`.
6. Icon: upload or paste a URL.
7. Save.

### Step 3 — Configure Grafana

In Grafana's `grafana.ini`:

```ini
[auth.generic_oauth]
enabled = true
name = authentik
client_id = <client-id-from-step-1>
client_secret = <client-secret-from-step-1>
scopes = openid profile email
auth_url = https://<authentik>/application/o/authorize/
token_url = https://<authentik>/application/o/token/
api_url = https://<authentik>/application/o/userinfo/
login_attribute_path = preferred_username
role_attribute_path = contains(groups[*], 'Grafana Admins') && 'Admin' || 'Viewer'
```

Restart Grafana, click "Sign in with authentik", and you're in.

> [!TIP]
> The OIDC discovery endpoint at `https://<authentik>/application/o/grafana/.well-known/openid-configuration` returns all URLs above as JSON — paste that into any modern OIDC client and it auto-configures.

### Step 4 — Restrict who can log in

1. **Customization → Policies → Create → Expression Policy**.
2. Expression:

   ```python
   return ak_is_group_member(request.user, name="Grafana Users")
   ```
3. Bind it: **Applications → Applications → Grafana → Policy / Group / User Bindings → Create binding** → Policy = the one you just made, Negate = no.

Now only members of the `Grafana Users` group will see Grafana in their User interface and be allowed to authenticate.

---

## 7. Flows and Stages

A **flow** is a recipe. Each step in the recipe is a **stage**. Flows are addressed in the URL: `/if/flow/<slug>/`.

### Default flows

| Slug | Purpose |
|---|---|
| `default-authentication-flow` | The main login flow. |
| `default-invalidation-flow` | Logout / session destroy. |
| `default-password-change-flow` | User-initiated password change. |
| `default-recovery-flow` | Forgot password. |
| `default-source-enrollment-flow` | First-time login from a federated source. |
| `default-source-authentication-flow` | Subsequent login from a federated source. |
| `default-provider-authorization-implicit-consent` | OAuth2 consent screen (auto-allow). |
| `default-provider-authorization-explicit-consent` | OAuth2 consent screen (show scopes). |
| `default-user-settings-flow` | The `/if/user/#/settings` self-service page. |
| `default-user-settings-flow` (MFA tab) | MFA enrollment/management. |

### All shipped stage types

Confirmed in `extracted/class_hierarchy.tsv` and the `authentik/stages/` directory tree:

| Stage | Purpose | License |
|---|---|---|
| `identification` | Collect username/email (and optionally show federated source buttons) | core |
| `password` | Validate the user's password | core |
| `prompt` | Generic form (collect arbitrary attributes) | core |
| `consent` | "Do you allow Grafana to read your email?" | core |
| `user_login` | Create the session | core |
| `user_logout` | Destroy the session | core |
| `user_write` | Persist collected attributes to a User object (enrollment, profile update) | core |
| `user_delete` | Delete a user account | core |
| `invitation` | Validate an invitation token | core |
| `deny` | Hard stop the flow (with custom message) | core |
| `redirect` | Send the user to an external URL | core |
| `email` | Send an email (verification, recovery) | core |
| `captcha` | Google reCAPTCHA / hCaptcha / Cloudflare Turnstile | core |
| `source` | Hand off to a federated source mid-flow | core |
| `authenticator_static` | Static one-time backup codes | core |
| `authenticator_totp` | TOTP enrollment (Google Authenticator, 1Password, etc.) | core |
| `authenticator_webauthn` | Passkey / FIDO2 / security-key enrollment | core |
| `authenticator_duo` | Duo Push enrollment | core |
| `authenticator_sms` | SMS code (Twilio or generic webhook) | core |
| `authenticator_email` | Email OTP | core |
| `authenticator_endpoint_gdtc` | Google Device Trust Connector | **Enterprise** |
| `authenticator_validate` | Validate any of the above MFA factors | core |
| `account_lockdown` | Lock account on policy match | **Enterprise** |
| `mtls` | Validate client cert (mutual TLS) | **Enterprise** |

### Editing a flow

1. **Flows and Stages → Flows → click the flow name**.
2. **Stage Bindings** tab — drag stages to reorder, click **Create binding** to add a new stage, edit the binding's *Order* number to reposition.
3. To gate a stage on a policy: edit the binding → set **Policy** → Save. Now the stage runs only if the policy returns `True`.

> [!TIP]
> Never edit a `default-*` flow directly. Clone it (**Duplicate** action), edit the clone, then point your application at the new flow. Defaults get re-applied on upgrade and your changes will vanish.

### Building a custom flow — example

A "passwordless WebAuthn" login flow:

1. Create flow: name `Passwordless`, slug `passwordless`, designation `Authentication`, layout `Stacked`.
2. Bind stages in order:
   - 10 — `default-authentication-identification` (Identification, but configure it to *not* show password)
   - 20 — `default-authentication-mfa-validation` (Authenticator Validation, accept only WebAuthn)
   - 30 — `default-authentication-login` (User Login)
3. Done. Send users to `/if/flow/passwordless/` to test.

---

## 8. Providers — Securing Apps

Providers are how external apps talk to authentik. Each app gets one provider. Pick the protocol the app supports.

### Available provider types

| Provider | Use when… | Notes |
|---|---|---|
| **OAuth2 / OpenID Connect** | The app is a modern web app or SPA, supports OIDC | Most common. Confidential or public client. Refresh tokens, device code grant, dynamic client registration via `/application/o/register/`. |
| **SAML 2.0** | The app is enterprise software (Atlassian, Salesforce, AWS IAM, Microsoft 365) | IdP-initiated and SP-initiated. Encryption + signing. Per-app NameID format. Redirect or POST binding. |
| **Proxy** | The app has no auth, but you can put a reverse proxy in front of it | Requires an outpost. Three modes: forward auth (Nginx/Traefik/Caddy), forward auth (single application), proxy. |
| **LDAP** | The app only speaks LDAP (e.g. Gitea, Nextcloud, Synology) | authentik becomes an LDAP *server*. Requires an LDAP outpost. Supports bind-DN and search-bind flows. |
| **RADIUS** | The app or appliance only speaks RADIUS (firewalls, VPNs, switches) | Requires a RADIUS outpost. PAP, CHAP, MS-CHAPv2. |
| **SCIM 2.0** | You want to push users/groups to an external SaaS that supports SCIM | Outbound provisioning. Schedules and reconciliation. |
| **Microsoft Entra ID** | You want to sync users/groups to Entra ID (formerly Azure AD) | **Enterprise**. Uses MS Graph. |
| **Google Workspace** | You want to sync users/groups to Google Workspace | **Enterprise**. Uses Admin SDK. |
| **WS-Federation** | The app speaks WS-Federation (legacy SharePoint, some .NET apps) | **Enterprise**. |
| **RAC (Remote Access Connection)** | You want browser-based RDP/SSH/VNC via Apache Guacamole-like UI | **Enterprise**. Requires a RAC outpost. |
| **SSF (Shared Signals Framework)** | You want to push session events to a SaaS that consumes SSF | **Enterprise**. Push-only. |

### OAuth2 / OIDC — endpoints

Auto-mounted by authentik when a provider exists:

| Endpoint | Path | Used by |
|---|---|---|
| Discovery | `/application/o/<slug>/.well-known/openid-configuration` | Most modern OIDC clients (just paste the URL) |
| Authorization | `/application/o/authorize/` | Browser-driven login |
| Token | `/application/o/token/` | Code → token exchange |
| UserInfo | `/application/o/userinfo/` | Token → user attributes |
| JWKS | `/application/o/<slug>/jwks/` | Token signature verification |
| End-session | `/application/o/<slug>/end-session/` | Single Logout |
| Introspection | `/application/o/introspect/` | Resource server token validation |
| Revoke | `/application/o/revoke/` | Refresh-token revocation |
| Device | `/application/o/device/` | Device code grant (TVs, CLIs) |

### SAML — metadata

```text
IdP metadata URL: https://<authentik>/api/v3/providers/saml/<id>/metadata/?download
SSO URL (redirect): https://<authentik>/application/saml/<slug>/sso/binding/redirect/
SSO URL (POST):     https://<authentik>/application/saml/<slug>/sso/binding/post/
SLO URL (redirect): https://<authentik>/application/saml/<slug>/slo/binding/redirect/
SLO URL (POST):     https://<authentik>/application/saml/<slug>/slo/binding/post/
```

Most SAML SPs will accept the metadata URL and configure themselves. If yours doesn't, download the XML and import it.

### Proxy — Forward Auth recipe (Traefik)

The Proxy provider lets you put authentik in front of any app, even ones with zero auth support, by terminating in an outpost and verifying every request.

```yaml
# traefik.yml — middleware
http:
  middlewares:
    authentik:
      forwardAuth:
        address: http://outpost.authentik.svc.cluster.local:9000/outpost.goauthentik.io/auth/traefik
        trustForwardHeader: true
        authResponseHeaders:
          - X-authentik-username
          - X-authentik-email
          - X-authentik-uid
          - X-authentik-groups
          - X-authentik-name
```

Then on any service:

```yaml
labels:
  - "traefik.http.routers.myapp.middlewares=authentik@file"
```

Now `myapp` requires an authentik login. The user's email, username, and groups are forwarded as headers — your app can use them or ignore them.

---

## 9. Sources — Authenticating Users

A **source** lets users sign into authentik with credentials from somewhere else. authentik supports both push-style (we ask them) and pull-style (they're already logged in elsewhere) federation.

### Available source types

| Source | Use when… | Notes |
|---|---|---|
| **LDAP** | You have Active Directory or OpenLDAP and want users to log in with their AD credentials | Periodic sync of users + groups. Detects deletions. Password validation at login time. |
| **OAuth2** | You want "Sign in with Google / GitHub / GitLab / Discord / Microsoft / Twitter / Twitch / Mailcow / Apple / Okta / Keycloak / Shibboleth / WeChat" | Each is a preconfigured type with the right OAuth client/scope defaults. Generic OAuth2 source for anything else. |
| **SAML** | You want users to sign in via an external SAML IdP (your customer's Okta, etc.) | Outbound metadata + inbound assertion validation. |
| **Kerberos** | Domain-joined Windows clients should SSO via GSSAPI/SPNEGO | Requires a keytab. Supports principal-name mapping. |
| **SCIM** | An external IdP pushes users/groups to *you* via SCIM 2.0 | Receive-side. Schemas and PATCH support. |
| **Plex** | You're building a Plex-centric service | Uses Plex's auth API. |
| **Telegram** | Telegram login widget | Uses Telegram's bot API. |

### Adding an LDAP source (Active Directory)

1. **Directory → Federation & Social login → Create → LDAP Source**.
2. **Server URI**: `ldap://dc1.example.com:389` or `ldaps://dc1.example.com:636`.
3. **Bind CN**: `cn=authentik,ou=Service Accounts,dc=example,dc=com` (a read-only service account).
4. **Bind Password**: the password for that account.
5. **Base DN**: `dc=example,dc=com`.
6. **User object filter**: `(objectClass=user)` (or `(&(objectClass=user)(sAMAccountName=*))` to skip computer accounts).
7. **Group object filter**: `(objectClass=group)`.
8. **Object uniqueness field**: `objectSid` for AD, `uid` for OpenLDAP, `entryUUID` for 389DS.
9. Save → click **Run sync now**.
10. Verify: **Directory → Users** now shows synced users with the source set to your LDAP source.

> [!TIP]
> Test the connection first without saving the source:
> ```bash
> docker compose exec worker ak ldap_check_connection <source-slug>
> ```
> The command is in `authentik/sources/ldap/management/commands/ldap_check_connection.py`. It prints `OK` or a Python-formatted error.

### Adding an OAuth2 source (GitHub)

1. **Federation & Social login → Create → OAuth Source → Provider type: GitHub**.
2. Get a Client ID/Secret from GitHub: **GitHub → Settings → Developer settings → OAuth Apps → New OAuth App**.
3. Authorization callback URL on GitHub: `https://<authentik>/source/oauth/callback/<source-slug>/`.
4. Paste Client ID + Secret into authentik. Save.
5. Edit your `default-source-enrollment-flow` and `default-source-authentication-flow` if you want custom enrollment behavior. Defaults are usually fine.
6. The "Sign in with GitHub" button now appears on your identification stage if you bound the source to it.

### Kerberos SSO (Active Directory desktops)

```bash
# 1. Create a keytab on a DC
ktpass -princ HTTP/[email removed] \
       -mapuser authentik$ -pass <password> \
       -crypto AES256-SHA1 -ptype KRB5_NT_PRINCIPAL \
       -out authentik.keytab

# 2. Copy keytab into the authentik worker:
docker cp authentik.keytab authentik-worker-1:/var/lib/authentik/

# 3. Federation → Create → Kerberos Source
#    keytab path: /var/lib/authentik/authentik.keytab
#    realm: EXAMPLE.COM

# 4. Test:
docker compose exec worker ak kerberos_check_connection <source-slug>
```

Then enable it in your identification stage's **Source settings** so the Kerberos button (or auto-SSO if you bind to MTLS) appears.

---

## 10. Outposts

An **outpost** is a separate process (Go binary, runs in its own container) that handles the wire protocol for non-HTTP providers. authentik core runs in Python; outposts run in Go because LDAP, RADIUS, RDP, and forward-auth need low-latency raw-protocol handling.

### Outpost types

Confirmed in the briefing's `module_index.cbf` (cmd/proxy, cmd/ldap, cmd/radius, cmd/rac, cmd/server):

| Outpost | Binary | Port | Used by |
|---|---|---|---|
| **Embedded** | inside the `server` container | shares 9000 | Proxy provider for single-host installs |
| **Proxy** | `cmd/proxy` | 9000/9443 | Proxy provider for separate deployment |
| **LDAP** | `cmd/ldap` | 3389/6636 (TLS) | LDAP provider |
| **RADIUS** | `cmd/radius` | 1812 (UDP) | RADIUS provider |
| **RAC** | `cmd/rac` | varies | RAC provider (Guacamole-style) |

### Embedded vs separate

- **Embedded** — easiest. Lives inside the main `server` container. Set the application's outpost = `authentik Embedded Outpost`. No extra deploy. Use for proxy providers only.
- **Separate** — required for LDAP, RADIUS, RAC, and recommended for production Proxy at scale. Deploy as a separate Docker container or Kubernetes pod. authentik can auto-deploy outposts to Docker or Kubernetes if you give the worker the necessary socket / kubeconfig access.

### Auto-deploying outposts via Docker

The default `compose.yml` mounts `/var/run/docker.sock` into the worker container. This lets authentik spin up outpost containers when you create a Proxy/LDAP/RADIUS/RAC provider and bind it to an outpost.

> [!WARNING]
> Mounting `/var/run/docker.sock` gives the worker container effective root on the host. If you can't accept that, use a [Docker Socket Proxy](https://github.com/Tecnativa/docker-socket-proxy) to restrict the operations, or [deploy outposts manually](https://docs.goauthentik.io/docs/add-secure-apps/outposts/manual-deploy-docker-compose).

### Manual outpost deployment

```yaml
# outpost-proxy.compose.yml
services:
  proxy:
    image: ghcr.io/goauthentik/proxy:2026.8.0-rc1
    ports:
      - 9000:9000
      - 9443:9443
    environment:
      AUTHENTIK_HOST: https://authentik.example.com
      AUTHENTIK_TOKEN: <outpost-token-from-admin-ui>
      AUTHENTIK_INSECURE: "false"
```

The outpost token is generated when you create the outpost in the Admin UI. Each outpost gets its own token.

---

## 11. Users, Groups, and RBAC

### Users

Three user types, set per-user:

| Type | Counts toward license? | Notes |
|---|---|---|
| `internal` | Yes | Regular user. Can log into the User interface. |
| `external` | Yes | Customer-facing user (B2C). Same capabilities as internal but billed differently in Enterprise. |
| `service_account` | No | API consumer. Cannot interactively log in. Holds tokens. |
| `internal_service_account` | No | Used by outposts. Created automatically. |

Switch a user's type:

```bash
docker compose exec worker ak change_user_type --type service_account alice
```

The command is in `authentik/core/management/commands/change_user_type.py:11`. Use `--all` to change every user (be careful).

### Groups

Groups can be local (created in authentik), synced (from an LDAP/SCIM/OAuth source), or hybrid. Group membership drives:

- **Application access** — bind a group to an application via a policy or directly via "Group Bindings".
- **RBAC roles** — assign a Role to a Group; everyone in the group gets the role.
- **SAML/OIDC claims** — the `groups` claim in OAuth2 access tokens, the SAML `MemberOf` attribute, and the LDAP-provider `memberOf` are all auto-populated from group membership.

### RBAC

**Roles** assign Django-Guardian object-level permissions. The bundled `authentik Admins` role grants full admin. You can create finer roles like "View users in `engineering` group only".

| Role permission scope | Notes |
|---|---|
| Global | Affects everything (e.g. `can_view_event`) |
| Per-model | Affects a model class (e.g. `can_change_user` on User) |
| Per-object | Affects a single instance (e.g. `can_change_user` on user-id=42) |

See **Directory → RBAC** in the Admin UI.

---

## 12. Policies and Property Mappings

### Policies

A policy is a function that returns `True` or `False`. You bind policies to flows, stages, application visibility, or source enrollment to gate access.

| Policy type | Returns True when… |
|---|---|
| **Expression** | A Python expression evaluates truthy. Has access to `request`, `ak_user_has_authenticator(...)`, `ak_is_group_member(...)`, `ak_logger`, and more. |
| **Event Matcher** | A specific event has occurred recently for this user (e.g. "5 failed logins in 1 hour"). |
| **Password** | Password meets complexity / age requirements. |
| **Reputation** | The user's IP/user reputation score is above a threshold (negative scores accumulate on failed logins). |
| **Expiry** | The user/password is older than N days. |
| **GeoIP** | Login is from a specific country/ASN. Requires GeoLite2 mounted at `/geoip/`. |
| **Dummy** | Always returns the value you configure. For testing. |
| **Unique Password** | **Enterprise**. Password has never been used by this user before. |

#### Expression policy — example

```python
# Block logins from non-corporate IPs unless user is in 'Remote Workers' group
from authentik.policies.utils import ak_is_group_member

if request.context.get("geoip") and request.context["geoip"]["country"] != "US":
    return ak_is_group_member(request.user, name="Remote Workers")
return True
```

Available helpers (documented in `website/docs/customize/policies/expression/`):

| Function | Use |
|---|---|
| `ak_is_group_member(user, name="...")` | Check membership by group name |
| `ak_user_has_authenticator(user, device_type="totp")` | Does the user have an MFA device? |
| `ak_logger.info("msg")` | Write to authentik's log |
| `ak_call_policy("slug", request=request)` | Compose policies — call one from another |
| `request.context["geoip"]` | IP geolocation, if enabled |
| `request.context["asn"]` | ASN data, if enabled |

### Property mappings

Property mappings transform user/group attributes into the shape your provider/source needs. They're tiny Python expressions.

#### OAuth2 scope mapping — example

```python
# Adds the user's department to the 'department' claim
return {
    "department": request.user.attributes.get("department", "unknown"),
    "manager": request.user.attributes.get("manager_email"),
}
```

#### SAML attribute mapping — example

```python
# Send memberships as a multi-valued SAML attribute
return [group.name for group in request.user.ak_groups.all()]
```

Mappings are listed under **Customization → Property Mappings**.

---

## 13. Blueprints — GitOps for authentik

Blueprints let you define every authentik object (Flow, Stage, Policy, Provider, Application, User, Group, Outpost) as YAML, apply them automatically on startup, and version-control them in git.

### A minimal blueprint

```yaml
version: 1
metadata:
  name: "my-grafana-app"
entries:
  - model: authentik_providers_oauth2.oauth2provider
    identifiers:
      name: "Grafana OAuth"
    attrs:
      client_type: "confidential"
      client_id: !Env GRAFANA_CLIENT_ID
      client_secret: !Env GRAFANA_CLIENT_SECRET
      redirect_uris:
        - matching_mode: strict
          url: "https://grafana.example.com/login/generic_oauth"
      signing_key: !Find [authentik_crypto.certificatekeypair, [name, "authentik Self-signed Certificate"]]
  - model: authentik_core.application
    identifiers:
      slug: "grafana"
    attrs:
      name: "Grafana"
      provider: !KeyOf provider-id
      meta_launch_url: "https://grafana.example.com"
```

### YAML tags

| Tag | What it does |
|---|---|
| `!Env VAR` | Read environment variable `VAR` |
| `!Find [model, [field, value]]` | Look up an existing object by field/value |
| `!KeyOf <id>` | Reference another object in the same blueprint by `identifiers.id` |
| `!Format ["template {0}", arg]` | Python `.format()` |
| `!If [condition, then, else]` | Conditional |
| `!Context key` | Read from the apply-time context |

### Applying blueprints

| How | What |
|---|---|
| Mounted at `/blueprints/` | Automatically discovered and applied on startup |
| `docker compose exec worker ak apply_blueprint <file>` | Manual apply, useful for testing |
| Via API: `POST /api/v3/managed/blueprints/` | Upload and apply at runtime |

The `apply_blueprint` command is at `authentik/blueprints/management/commands/apply_blueprint.py:36`.

> [!TIP]
> Bundled blueprints live at `blueprints/default/`, `blueprints/example/`, and `blueprints/system/`. Don't edit those — copy what you need into a new file under your own `/blueprints/custom/` mount.

### Example library

The `blueprints/example/` directory ships ~20 ready-to-adapt blueprints covering common SaaS apps (Slack, Nextcloud, AWS, GitLab, etc.) plus all the protocol patterns. Browse the [GitHub directory](https://github.com/goauthentik/authentik/tree/main/blueprints/example) for the full list.

---

## 14. Customization and Branding

### Brands (multi-tenancy)

A **Brand** is a subdomain-scoped configuration: logo, colors, default flows, footer links. Use this to run multiple "instances" of authentik from one deployment.

1. **System → Brands → Create**.
2. Domain: `customer-a.auth.example.com`.
3. Default flows: set the four flows (auth, invalidation, recovery, unenrollment) you want this brand to use.
4. Branding: set logo, favicon, title, additional metadata.
5. Save.

When a request hits `customer-a.auth.example.com`, authentik will use this brand's flows and visual settings.

### Custom CSS

**System → Brands → \<your brand\> → Custom CSS**:

```css
:root {
    --pf-global--primary-color--100: #1f2937;
    --pf-global--BackgroundColor--dark-100: #111827;
}
.pf-c-login__main-header img { max-height: 80px; }
```

Applied immediately to all flow and admin interfaces under this brand.

### Email templates

Mount custom templates at `/templates/` in the worker container, then reference them in an Email stage's "Template" dropdown. See `authentik/stages/email/templates/` for the structure to clone.

---

## 15. Events and Notifications

Every authentik action emits an event. Events are stored in PostgreSQL and queryable from the Admin UI.

### Event types (sampled from `extracted/class_hierarchy.tsv`)

| Action | Trigger |
|---|---|
| `login` / `login_failed` | User authentication attempt |
| `logout` | Session destruction |
| `user_write` | User attribute change |
| `password_set` | Password change |
| `secret_view` | Sensitive value shown in UI |
| `configuration_error` | Misconfiguration detected |
| `policy_execution` | Policy evaluated |
| `model_created` / `model_updated` / `model_deleted` | DB change |
| `update_available` | New authentik version released |
| `impersonation_started` / `impersonation_ended` | Admin impersonated a user |
| `flow_execution` | Flow ran |
| `source_linked` | User completed federated enrollment |

### Notification rules

1. **System → Events → Notification Rules → Create**.
2. Event matcher policy: e.g. `EventMatcher` policy for `login_failed`.
3. Group to notify: e.g. `Security Admins`.
4. Severity, transport (email, webhook, Sentry).
5. Save.

### Webhook transports

```yaml
# Notification Transport: Webhook (Generic)
URL: https://chat.example.com/hooks/incoming/xyz
Webhook Body:
{
  "text": "{{ notification.body }}",
  "user": "{{ notification.user.username }}",
  "severity": "{{ notification.severity }}"
}
```

Slack-formatted and Discord-formatted variants ship as separate transport types.

---

## 16. Backups, Upgrades, and Air-Gapped Installs

### Backups

authentik state lives in two places:

1. **PostgreSQL** — every object, every user, every flow, every event. Back it up with `pg_dump`:

   ```bash
   docker compose exec postgresql pg_dump -U authentik authentik > authentik-$(date +%F).sql
   ```

2. **`/data` volume** — uploaded files (icons, flow backgrounds, CSV reports). `tar` it:

   ```bash
   docker compose exec server tar -C /data -czf - . > authentik-data-$(date +%F).tar.gz
   ```

Restore: stop authentik, `psql < dump.sql` and untar the data volume back.

### Upgrades

```bash
wget -O compose.yml https://docs.goauthentik.io/compose.yml
docker compose pull
docker compose up -d
```

Migrations run automatically in the worker on container start. Watch the worker logs:

```bash
docker compose logs -f worker | grep -E "(migrat|error)"
```

> [!IMPORTANT]
> Always read the release notes at [docs.goauthentik.io/docs/releases](https://docs.goauthentik.io/docs/releases) *before* upgrading. Some releases introduce breaking changes (e.g. `2026.5.0` changed the email password-reset template — see [#22757](https://github.com/goauthentik/authentik/issues/22757)). Migrations are forward-only.

### Air-gapped installs

Set in `.env`:

```bash
AUTHENTIK_DISABLE_UPDATE_CHECK=true
AUTHENTIK_DISABLE_STARTUP_ANALYTICS=true
AUTHENTIK_ERROR_REPORTING__ENABLED=false
```

Mirror the container images to an internal registry. authentik does not call home for license validation in core; the Enterprise license server is checked periodically — see [docs.goauthentik.io/docs/install-config/air-gapped](https://docs.goauthentik.io/docs/install-config/air-gapped) for the full offline procedure.

---

## 17. Common Tasks (Recipes)

### Enable MFA for a specific group

1. Create an Expression Policy:

   ```python
   return ak_is_group_member(request.user, name="MFA Required")
   ```
2. Edit `default-authentication-flow` → Stage Binding for `default-authentication-mfa-validation` → set this policy. Now MFA validation runs only for users in the `MFA Required` group.

### Force password rotation every 90 days

1. **Customization → Policies → Create → Password Expiry Policy** → Days: 90.
2. Edit `default-authentication-flow` → after Password stage, add a stage binding for `default-password-change-prompt` (or a custom prompt) gated by this policy.

### Auto-enrol new users from an OAuth source into a group

1. **Federation & Social login → \<your source\> → Property Mapping**:

   ```python
   # Enrollment Mapping
   from authentik.core.models import Group
   default_group, _ = Group.objects.get_or_create(name="Auto-enrolled")
   return {"ak_groups": [default_group]}
   ```
2. Bind this mapping to the source's enrollment flow.

### Use an external PostgreSQL

In `.env`:

```bash
AUTHENTIK_POSTGRESQL__HOST=db.example.com
AUTHENTIK_POSTGRESQL__PORT=5432
AUTHENTIK_POSTGRESQL__NAME=authentik
AUTHENTIK_POSTGRESQL__USER=authentik
AUTHENTIK_POSTGRESQL__PASSWORD=<password>
AUTHENTIK_POSTGRESQL__SSLMODE=require
```

Then remove the `postgresql` service from `compose.yml`. The same env keys exist for read replicas via `AUTHENTIK_POSTGRESQL__READ_REPLICAS__0__HOST` etc. (see `authentik/lib/default.yml` lines 18-31).

### Run with FIPS-compliant crypto

`authentik/lib/default.yml` exposes:

```yaml
compliance:
  fips:
    enabled: false   # set true via AUTHENTIK_COMPLIANCE__FIPS__ENABLED=true
```

The Rust outposts already link `aws-lc-rs` with the `fips` feature (see `Cargo.toml`). When `compliance.fips.enabled=true`, authentik also restricts TLS cipher suites and refuses non-FIPS modules at startup.

### Export your config as blueprints

```bash
docker compose exec worker ak apply_blueprint --export > my-config.yaml
```

This roundtrips every flow/stage/provider/policy currently in the DB into a blueprint you can commit to git.

### Generate or rotate an admin recovery key

```bash
docker compose exec worker ak create_recovery_key 60 akadmin
```

`60` is duration in minutes (so 1 hour). The output URL bypasses MFA and password — store it offline.

### Test an SMTP setup without sending real reset emails

```bash
docker compose exec worker ak test_email [email removed] -S default-email-stage
```

(From `authentik/stages/email/management/commands/test_email.py`.)

### Apply schema changes to your IDE — generate Python types

```bash
docker compose exec worker ak build_schema --api-file schema.yml
```

Then download `schema.yml`, run `openapi-generator` against it for your language of choice. (The TypeScript client ships pre-generated at `packages/client-ts/`; Go client at `packages/client-go/`; Rust client at `packages/client-rust/`.)

---

## 18. CLI Reference (`ak`)

All commands run inside the `worker` container: `docker compose exec worker ak <command>`. The full list is auto-discovered from Django management commands under `authentik/*/management/commands/`. The most useful ones, from `extracted/cli_commands.tsv`:

| Command | Purpose | Source |
|---|---|---|
| `ak apply_blueprint <file>` | Apply a blueprint file (or `--export` to dump current state) | `authentik/blueprints/management/commands/apply_blueprint.py:36` |
| `ak benchmark -p 4 --csv` | Stress-test flow execution. `-p` = parallel processes | `authentik/flows/management/commands/benchmark.py:56-63` |
| `ak build_schema --api-file schema.yml` | Regenerate the OpenAPI spec | `authentik/api/management/commands/build_schema.py:20-21` |
| `ak change_user_type --type <type> --all <usernames>` | Convert user(s) between internal / external / service_account | `authentik/core/management/commands/change_user_type.py:11-13` |
| `ak create_admin_group <user>` | Add a user to the bootstrap admins group | `authentik/recovery/management/commands/create_admin_group.py:18` |
| `ak create_recovery_key <minutes> <user>` | Generate a one-shot recovery URL | `authentik/recovery/management/commands/create_recovery_key.py:29-36` |
| `ak hash_password <password>` | Hash a password as authentik stores it (for blueprint seeding) | `authentik/core/management/commands/hash_password.py:13` |
| `ak import_certificate --name <n> --certificate <pem> --private-key <pem>` | Import a TLS cert/key pair | `authentik/crypto/management/commands/import_certificate.py:51-53` |
| `ak kerberos_check_connection <source-slug>` | Smoke-test a Kerberos source | `authentik/sources/kerberos/management/commands/kerberos_check_connection.py:17` |
| `ak kerberos_sync <source-slug>` | Run an on-demand Kerberos sync | `authentik/sources/kerberos/management/commands/kerberos_sync.py:16` |
| `ak ldap_check_connection <source-slug>` | Smoke-test an LDAP source | `authentik/sources/ldap/management/commands/ldap_check_connection.py:17` |
| `ak ldap_sync <source-slug>` | Run an on-demand LDAP sync | `authentik/sources/ldap/management/commands/ldap_sync.py:15` |
| `ak scim_sync <provider-slug>` | Run an on-demand SCIM provisioning sync (outbound) | `authentik/providers/scim/management/commands/scim_sync.py:15` |
| `ak set_flag <flag_key> <flag_value>` | Toggle a tenant feature flag | `authentik/tenants/management/commands/set_flag.py:11-12` |
| `ak test_email <to> -S <stage-slug>` | Send a test email through a configured email stage | `authentik/stages/email/management/commands/test_email.py:49-50` |
| `ak clean_orphan_obj_perms --batch-size N` | Clean up Guardian object permissions for deleted users | `packages/ak-guardian/guardian/management/commands/clean_orphan_obj_perms.py:25-40` |

Also bundled but rarely user-facing: `dumpdata`, `loaddata`, `makemigrations`, `migrate`, `runserver`, `shell` — all standard Django management commands.

### Outpost binaries

| Binary | Cobra-style commands | Source |
|---|---|---|
| `authentik-proxy` | `serve`, `healthcheck` | `cmd/proxy/main.go` |
| `authentik-ldap` | `serve`, `healthcheck` | `cmd/ldap/main.go` |
| `authentik-radius` | `serve`, `healthcheck` | `cmd/radius/main.go` |
| `authentik-rac` | `serve`, `healthcheck` | `cmd/rac/main.go` |
| `authentik-server` | `serve`, `healthcheck`, `worker` | `cmd/server/main.go`, `cmd/server/server.go:26`, `cmd/server/healthcheck.go:20` |

All Go binaries respect `AUTHENTIK_*` env vars the same way the Python server does (parsed by `internal/config/config.go`).

---

## 19. Troubleshooting

### Error: `Migration authentik_core.0058_setup fails on upgrade`

**Cause.** Upgrading directly across more than one minor release without intermediate migrations applied. Confirmed in [#22688](https://github.com/goauthentik/authentik/issues/22688) (upgrade from `2025.10.2 → 2026.5.0`).

**Solution.** Roll back to your previous tag, then upgrade one minor release at a time:

```bash
# Restore the previous compose.yml
git checkout HEAD~1 compose.yml
docker compose up -d
# Wait for healthy, then upgrade to the next minor
sed -i 's|:2025.10.2|:2025.12.5|' compose.yml
docker compose pull && docker compose up -d
# Repeat for each minor up to 2026.5.0
```

**Prevention.** Read the [release notes](https://docs.goauthentik.io/docs/releases) before every upgrade; skip-version upgrades are explicitly unsupported.

### Error: `No identification data provided` after upgrading 2FA

**Cause.** Bug introduced in `2025.12.1` affecting Authenticator Validation Stage configurations. Tracked in [#19552](https://github.com/goauthentik/authentik/issues/19552) (43 community comments — fix landed in `2026.1.0`).

**Solution.** Upgrade to `2026.1.0` or later. Workaround on the affected version: edit your authentication flow's Authenticator Validation stage and re-save it (resets the internal device-class set).

**Prevention.** Stick to `.0` / `.x` patch versions on production; let `.0-rc` and `.0` settle before adopting.

### Error: Starting the worker makes the server not load

**Cause.** Resource contention or a worker that holds the DB exclusive lock during a long-running migration. Closed in [#22606](https://github.com/goauthentik/authentik/issues/22606); fix backported.

**Solution.** Restart in order: stop both, start `server` and `postgresql`, wait for `server` to be healthy, then start `worker`. If migrations still hang, `docker compose logs worker` to see which migration is stuck.

**Prevention.** Pin pre-release tags only in staging.

### Error: SAML provider not JSON serializable (`2026.5.0`)

**Cause.** Regression in `Event.log_deprecation` when the SAML provider uses redirect binding. Closed in [#22694](https://github.com/goauthentik/authentik/issues/22694) — patched in `2026.5.1`.

**Solution.** Upgrade to `2026.5.1` or later.

**Prevention.** Subscribe to release announcements; this was hotfixed within 48 hours.

### Error: OAuth single logout broken (`2026.5.0`)

**Cause.** Front-channel logout regression. Tracked in [#22572](https://github.com/goauthentik/authentik/issues/22572) (14 comments, still open at time of writing).

**Solution.** Workaround: switch the OAuth provider to back-channel logout in the Provider's "Logout settings" tab. Long-term fix is pending.

**Prevention.** Test logout flows in staging before upgrading.

### Error: LDAP Provider not returning expected results

**Cause.** Various root causes — most commonly missing `objectClass` mappings or incorrect search base. See [#22743](https://github.com/goauthentik/authentik/issues/22743) (closed).

**Solution.**
1. Check the LDAP outpost is connected: **Admin → Applications → Outposts** → the outpost should show "Last seen" within 60 seconds.
2. Try the search manually:
   ```bash
   ldapsearch -H ldap://outpost.example.com:3389 \
     -D "cn=service-account,ou=users,dc=ldap,dc=goauthentik,dc=io" \
     -w '<token>' -b "dc=ldap,dc=goauthentik,dc=io" \
     "(objectClass=user)"
   ```
3. Check the outpost logs: `docker compose logs ldap-outpost`.

**Prevention.** Keep your LDAP provider's "Search group" pointing at a small test group during initial setup; expand only after the smoke test passes.

### Error: Admin interface crashes in Safari

**Cause.** Web component bug in older Safari versions. Tracked in [#19166](https://github.com/goauthentik/authentik/issues/19166) (8 comments) → references [#14322](https://github.com/goauthentik/authentik/issues/14322).

**Solution.** Use Firefox or Chrome for the Admin interface. The User interface is unaffected. Upgrade Safari to 17+.

**Prevention.** Test admin workflows in the browsers your team actually uses.

### Error: "Refresh" on Outposts page doesn't refresh state

**Cause.** UI bug — the button triggers a fetch but doesn't re-render the list. Tracked in [#19951](https://github.com/goauthentik/authentik/issues/19951).

**Solution.** Reload the whole page (`F5`). The data is updated; only the visual update is broken.

**Prevention.** N/A — cosmetic.

### Error: authentik redirects to interface instead of app

**Cause.** Application launch URL not set or set incorrectly. Tracked in [#18450](https://github.com/goauthentik/authentik/issues/18450).

**Solution.** Edit the Application → **Launch URL** must be the public URL of the app, not authentik's URL. For OAuth apps, this is what the user lands on after consent.

**Prevention.** Always test the Launch URL by clicking the app tile in the User interface after creation.

### Error: Custom user attributes prevent invitation creation

**Cause.** Wizard validation bug when the User model has required custom attributes. Tracked in [#22637](https://github.com/goauthentik/authentik/issues/22637).

**Solution.** Use the API directly to create the invitation:

```bash
curl -X POST -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  https://<authentik>/api/v3/stages/invitation/invitations/ \
  -d '{"name":"new-user","flow":"<uuid>","fixed_data":{"email":"[email removed]"}}'
```

**Prevention.** Mark custom user attributes as nullable in your prompt stage until the wizard fix lands.

### Error: Cannot import the device-code-flow blueprint

**Cause.** The blueprint file location moved between releases. Tracked in [#19525](https://github.com/goauthentik/authentik/issues/19525).

**Solution.** Use the bundled blueprint via `apply_blueprint`:

```bash
docker compose exec worker ak apply_blueprint default/flow-device-code-authorization.yaml
```

**Prevention.** Reference blueprints via their canonical name (`default/...`) rather than downloading them.

### Error: Email password-reset uses naturaltime ungrammatically

**Cause.** Template uses Django's `naturaltime` filter which produces "now", "1 day", "2 days" — readable in English but ungrammatical when used as in "Token expires {{ ... }}". Tracked in [#22757](https://github.com/goauthentik/authentik/issues/22757).

**Solution.** Override the template by mounting your own at `/templates/email/password_reset.html` in the worker container.

**Prevention.** Custom email templates are a one-time fix.

### Error: outpost shows disconnected immediately after creation

**Cause.** Outpost token not propagated yet (auto-deploy mode), or the outpost can't reach `AUTHENTIK_HOST`.

**Solution.**
1. Check the outpost logs: `docker logs <outpost-container>`. Look for "connected" or any TLS/network errors.
2. Verify `AUTHENTIK_HOST` is reachable from the outpost: `docker exec <outpost> wget -qO- $AUTHENTIK_HOST/-/health/ready/`.
3. If the outpost is in a different network/cluster, set `AUTHENTIK_HOST_BROWSER` to the user-facing URL and `AUTHENTIK_HOST` to the internal URL.

**Prevention.** Always set `AUTHENTIK_HOST_BROWSER` separately when behind a reverse proxy.

### Error: PostgreSQL "password too long"

**Cause.** PostgreSQL's libpq does not accept passwords > 99 chars.

**Solution.** Regenerate `PG_PASS` with a shorter length:

```bash
sed -i '/^PG_PASS=/d' .env
echo "PG_PASS=$(openssl rand -base64 36 | tr -d '\n')" >> .env
```

If you've already run with the long password, you'll also need to `ALTER USER` in PostgreSQL to match.

**Prevention.** Stick with the documented `openssl rand -base64 36` recipe.

### Error: All users sign in but no apps appear

**Cause.** Users are authenticated but no Application is bound to their group / a policy is denying visibility.

**Solution.**
1. **Admin → Applications → Applications → \<your app\> → Policy Bindings** — check if there's a deny policy.
2. **Directory → Users → \<user\> → see "Applications"** tab — what does this user actually have access to?
3. If empty, add a group binding from a group that includes the user.

**Prevention.** When creating an app, always set at least one group/user binding immediately, or accept "no policy = visible to everyone".

---

## 20. FAQ

**Q: Is authentik really free? What does Enterprise add?**
Yes — the core (everything in `authentik/` except `authentik/enterprise/`) is MIT-licensed and free forever. Enterprise adds: SSF, MS Entra/Google Workspace sync, audit logging, MTLS stage, RAC provider, account lockdown stage, unique-password policy, and a Support center. Pricing at [goauthentik.io/pricing](https://goauthentik.io/pricing).

**Q: Do I need Redis?**
No. Current authentik uses PostgreSQL `LISTEN/NOTIFY` for pub/sub and PostgreSQL for caching via `django-postgres-cache`. PostgreSQL is the sole stateful dependency.

**Q: How many users can authentik handle?**
With the Docker Compose default (single server + worker, bundled PostgreSQL) you can serve thousands of users comfortably. For tens of thousands, switch to Kubernetes Helm with `replicaCount > 1` and an external PostgreSQL with read replicas. authentik scales horizontally — both `server` and `worker` are stateless except for the embedded outpost.

**Q: Can authentik replace Okta / Entra ID / Auth0?**
For most SAML and OIDC use cases, yes. For B2C at internet scale (millions of users) you'll want to tune PostgreSQL aggressively. For specific Okta features like AD agent or feature flags, you'd need to build them — authentik covers the protocol surface but Okta has a lot of integration glue you'd reimplement.

**Q: How do passwordless / passkey / WebAuthn work?**
Configure the `authenticator_webauthn` stage and bind it as the only required MFA in your flow. Skip the password stage entirely (or make it conditional on absence of WebAuthn). The user enrolls a passkey once, then logs in with just username + passkey.

**Q: Can I use authentik with a custom domain per tenant?**
Yes — that's exactly what **Brands** are for. Create one Brand per domain. Each brand can override the default flows, branding, and metadata.

**Q: Does authentik support SCIM both ways?**
Yes. As a **provider** (push users to Slack/Zoom/etc.) and as a **source** (Okta pushes users to you). Both implement SCIM 2.0 with PATCH semantics.

**Q: Where does authentik store sessions?**
PostgreSQL. The session table is `authentik_core_authenticatedsession`. Cookies are HTTP-only, Secure (over HTTPS), with `SameSite=Lax` by default. The session-cookie age is configurable per brand.

**Q: Can users self-register?**
Yes — bind a `user_write` stage at the end of an enrollment flow and link to it from your identification stage. Be aware that without an Email or Captcha stage, you're exposing user creation publicly.

**Q: How do I migrate from Keycloak / Authelia / Authentik?**
There's no automated importer. Inventory your Realms → Brands, Clients → Providers, Users → exported user CSV (use `ak loaddata`), then test SSO with one app before flipping the cutover.

**Q: Does authentik have an audit log?**
The Events system covers most of what you'd want: every login, model change, configuration change, impersonation, etc. The Enterprise audit log adds tamper-evidence, retention controls, and structured export.

**Q: How do I export and import a single flow?**
`ak apply_blueprint --export > all.yaml`, then hand-extract the flow's section. There's also an API endpoint: `GET /api/v3/managed/blueprints/?model=authentik_flows.flow` returns blueprint fragments per flow.

**Q: Can authentik act as an OIDC client for other apps' authentication?**
No — authentik is an IdP, not an SP. To pull users from Google/GitHub/etc., use Sources, not Providers.

**Q: How do I integrate with Cloudflare Access?**
Use a Proxy provider in "Forward auth (Cloudflare)" mode, then configure Cloudflare to call authentik's auth endpoint. Full recipe at [docs.goauthentik.io/integrations/security/cloudflare-access](https://docs.goauthentik.io/integrations/security/cloudflare-access/).

**Q: Where are the integration recipes?**
[goauthentik.io/integrations](https://goauthentik.io/integrations) — the `website/integrations/` directory in the source has a one-pager per supported third-party app (~150+ integrations as of `2026.8.0`).

**Q: How do I delete a user but keep their audit trail?**
Use a `user_delete` stage in a custom flow, or Admin → Directory → Users → checkbox → "Disable" (preserves the user record but blocks login). Hard deletion removes the user from Events too, which usually isn't what you want.

**Q: What's the relationship between a Flow and a Brand?**
A Brand has four "default flow" slots (authentication, invalidation, recovery, unenrollment). When a request hits that brand's domain, authentik uses those flows. The Flow itself is brand-agnostic — one flow can be the default for many brands.

**Q: Can I run authentik behind Cloudflare?**
Yes. Set `AUTHENTIK_LISTEN__TRUSTED_PROXY_CIDRS` to include Cloudflare's IP ranges (or `0.0.0.0/0` if you trust your network), and verify `X-Forwarded-For` reaches authentik correctly.

**Q: How do I rotate the signing certificate for OIDC?**
Generate a new certificate (Admin → System → Certificates → Generate or upload), then edit your OAuth provider and switch its "Signing Key" to the new one. Existing tokens signed with the old key stay valid until they expire; clients that use JWKS will refresh automatically.

**Q: Does authentik support hardware-based MFA (Yubikey)?**
Yes — Yubikeys appear as WebAuthn devices when used in WebAuthn mode. Plug in, register via the authenticator-webauthn stage, done. Yubikey OTP (not WebAuthn) isn't separately supported.

---

## 21. Reference Tables

### Container ports

| Port | Protocol | Used by |
|---|---|---|
| 9000 | HTTP | Server (web UI + API + embedded outpost) |
| 9443 | HTTPS | Server (web UI + API + embedded outpost) |
| 3389 | LDAP | LDAP outpost (StartTLS supported) |
| 6636 | LDAPS | LDAP outpost (TLS) |
| 1812 | RADIUS (UDP) | RADIUS outpost |
| 9300 | HTTP | Prometheus metrics (`/metrics`) |
| 9900 | HTTP | Debug endpoint (`internal/debug/debug.go`, pprof + dump_config; disabled in prod) |

Defaults from `authentik/lib/default.yml` lines 36-49.

### Health endpoints

| Path | Returns |
|---|---|
| `/-/health/live/` | `200` if process is running |
| `/-/health/ready/` | `200` if DB + worker connected and migrations done |
| `/-/metrics/` | Prometheus format (on port `:9300` by default) |

### Useful env vars (`AUTHENTIK_*` namespace)

All keys in `default.yml` are env-overridable. Nesting uses `__`. Examples:

| Env var | What it sets |
|---|---|
| `AUTHENTIK_SECRET_KEY` | Django secret key (required) |
| `AUTHENTIK_POSTGRESQL__HOST` | PostgreSQL host |
| `AUTHENTIK_POSTGRESQL__PORT` | PostgreSQL port |
| `AUTHENTIK_POSTGRESQL__NAME` | DB name |
| `AUTHENTIK_POSTGRESQL__USER` | DB user |
| `AUTHENTIK_POSTGRESQL__PASSWORD` | DB password (also via `env://POSTGRES_PASSWORD`) |
| `AUTHENTIK_POSTGRESQL__SSLMODE` | `require`, `prefer`, `disable` |
| `AUTHENTIK_LISTEN__HTTP` | `[::]:9000` |
| `AUTHENTIK_LISTEN__HTTPS` | `[::]:9443` |
| `AUTHENTIK_LISTEN__TRUSTED_PROXY_CIDRS` | comma-separated CIDR list |
| `AUTHENTIK_LOG_LEVEL` | `debug`, `info`, `warning`, `error` |
| `AUTHENTIK_ERROR_REPORTING__ENABLED` | `true` enables Sentry |
| `AUTHENTIK_EMAIL__HOST` | SMTP host |
| `AUTHENTIK_DISABLE_UPDATE_CHECK` | `true` for air-gapped |
| `AUTHENTIK_DISABLE_STARTUP_ANALYTICS` | `true` for air-gapped |
| `AUTHENTIK_OUTPOSTS__CONTAINER_IMAGE_BASE` | Override outpost image prefix (e.g. for internal registry) |
| `AUTHENTIK_OUTPOSTS__DISCOVER` | `true` (default) to auto-discover Docker/Kubernetes |
| `AUTHENTIK_OUTPOSTS__DISABLE_EMBEDDED_OUTPOST` | `true` to disable the in-server proxy |
| `AUTHENTIK_BLUEPRINTS_DIR` | Path to mounted blueprints (default `/blueprints`) |
| `AUTHENTIK_CERT_DISCOVERY_DIR` | Path watched for external certs (default `/certs`) |
| `AUTHENTIK_WEB__WORKERS` | Gunicorn worker processes (default 2) |
| `AUTHENTIK_WEB__THREADS` | Threads per worker (default 4) |
| `AUTHENTIK_WORKER__PROCESSES` | Dramatiq worker processes (default 1) |
| `AUTHENTIK_WORKER__THREADS` | Threads per worker (default 2) |
| `AUTHENTIK_COMPLIANCE__FIPS__ENABLED` | `true` for FIPS mode |
| `AUTHENTIK_TENANTS__ENABLED` | `true` for multi-tenant (different from Brands) |
| `AUTHENTIK_GEOIP` | Override `/geoip/GeoLite2-City.mmdb` path |

For the complete keyspace, run `docker compose exec server cat /etc/authentik/config.yml` after install — every default is enumerated.

### Default flow slugs (in `blueprints/default/`)

| Slug | Purpose |
|---|---|
| `default-authentication-flow` | Login |
| `default-invalidation-flow` | Logout |
| `default-password-change-flow` | Password change |
| `default-recovery-flow` | Forgot password |
| `default-source-enrollment-flow` | First federated login → create local user |
| `default-source-authentication-flow` | Subsequent federated login |
| `default-source-pre-authentication-flow` | Pre-source decisions (e.g. terms of service) |
| `default-provider-authorization-implicit-consent` | Auto-allow OAuth consent |
| `default-provider-authorization-explicit-consent` | Show OAuth scope screen |
| `default-provider-invalidation-flow` | Logout for OIDC RP-initiated logout |
| `default-user-settings-flow` | `/if/user/#/settings` page |
| `default-user-settings-flow` (MFA tab) | Enroll/manage MFA devices |
| `default-flow-pre-authentication` | Run before any other authentication (e.g. brand-level CAPTCHA) |

### Key file paths inside the container

| Path | Contains |
|---|---|
| `/etc/authentik/config.yml` | Merged effective config (read-only, regenerated on start) |
| `/blueprints/` | Mount your custom YAML here |
| `/data/` | Uploaded files (icons, backgrounds, CSV exports) |
| `/certs/` | Externally-discovered cert pairs (`<name>.crt` + `<name>.key`) |
| `/templates/` | Custom email templates |
| `/geoip/` | `GeoLite2-City.mmdb` + `GeoLite2-ASN.mmdb` |
| `/lifecycle/` | Startup scripts and migrations |

### Where to learn more

- **Official docs**: [docs.goauthentik.io](https://docs.goauthentik.io)
- **Integration recipes**: [goauthentik.io/integrations](https://goauthentik.io/integrations)
- **Discord**: [goauthentik.io/discord](https://goauthentik.io/discord)
- **GitHub Issues**: [github.com/goauthentik/authentik/issues](https://github.com/goauthentik/authentik/issues)
- **Release notes**: [docs.goauthentik.io/docs/releases](https://docs.goauthentik.io/docs/releases)
- **Developer Documentation**: [docs.goauthentik.io/docs/developer-docs](https://docs.goauthentik.io/docs/developer-docs)
- **Helm chart**: [github.com/goauthentik/helm](https://github.com/goauthentik/helm)

For an architectural deep-dive — request flow, code layout, the outpost wire protocol, blueprint internals, performance tuning, and security posture — see the **Admin & Developer Guide**.

---

_Generated May 2026 from goauthentik/authentik at 9e75f62. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
