Every self-hosted application comes with its own login page, its own user database, and its own password to remember. Ten services means ten separate credentials, ten sessions to manage, and ten places where a compromised password could grant unauthorized access. Authentik solves this by providing a centralized identity provider — one login for everything, with single sign-on, multi-factor authentication, and a complete audit trail of who accessed what and when.

Running your own identity provider on an Ubuntu VPS gives you the security and convenience of enterprise SSO without depending on Okta, Auth0, or Google for authentication. Your credentials never leave your infrastructure, and you control the authentication policies, MFA requirements, and access rules that govern every application in your self-hosted stack.

MassiveGRID Ubuntu VPS includes: Ubuntu 24.04 LTS pre-installed · Proxmox HA cluster with automatic failover · Ceph 3x replicated NVMe storage · Independent CPU/RAM/storage scaling · 12 Tbps DDoS protection · 4 global datacenter locations · 100% uptime SLA · 24/7 human support rated 9.5/10

Deploy a self-managed VPS — from $1.99/mo
Need dedicated resources? — from $19.80/mo
Want fully managed hosting? — we handle everything

Why Centralized Authentication Matters

Without a centralized identity provider, your self-hosted infrastructure accumulates security debt with every new application:

A centralized identity provider eliminates all of this. One account, one password, one MFA enrollment, one place to revoke access, and one audit log for everything.

Authentik vs Authelia vs Keycloak

Three identity providers dominate the self-hosting space. Here is how they compare:

Authelia — lightweight forward-auth proxy. Excellent for adding authentication to applications that lack their own login systems. Limited in that it cannot act as a full OIDC/OAuth2 provider or LDAP server. Best for simple setups with few applications that lack native SSO support.

Keycloak — enterprise Java-based identity platform by Red Hat. Extremely powerful but resource-heavy (1–2GB RAM just for Keycloak itself) and complex to configure. The admin interface has hundreds of options that can overwhelm self-hosters. Best for organizations with dedicated IT staff.

Authentik — modern, Python-based identity provider that hits the sweet spot. Full OIDC/OAuth2 provider, SAML support, LDAP outpost, forward-auth proxy, customizable login flows, and an intuitive admin interface. Resource requirements are moderate, and the documentation is written for self-hosters rather than enterprise admins. This is the right choice for most self-hosted environments.

Prerequisites

Authentik runs alongside your other self-hosted services on Docker. Install Docker first using our Docker installation guide for Ubuntu VPS, and set up Nginx by following our Nginx reverse proxy guide.

Authentik runs alongside your other self-hosted services. It requires about 1GB RAM for the main application plus PostgreSQL and Redis — a Cloud VPS with 2 vCPU / 4GB RAM provides comfortable headroom for Authentik alongside several other Docker services.

Docker Compose Setup

Create the Authentik directory and generate the required secret key:

mkdir -p /opt/authentik && cd /opt/authentik

# Generate a secret key (do not change this after initial setup)
echo "AUTHENTIK_SECRET_KEY=$(openssl rand -base64 36)" >> .env
echo "PG_PASS=$(openssl rand -base64 24)" >> .env

Review and extend the environment file:

cat >> /opt/authentik/.env << 'EOF'
# Authentik configuration
AUTHENTIK_ERROR_REPORTING__ENABLED=false
COMPOSE_PORT_HTTP=9000
COMPOSE_PORT_HTTPS=9443

# Email (optional, for password reset and notifications)
# AUTHENTIK_EMAIL__HOST=smtp.yourdomain.com
# AUTHENTIK_EMAIL__PORT=587
# AUTHENTIK_EMAIL__USERNAME=authentik@yourdomain.com
# AUTHENTIK_EMAIL__PASSWORD=smtp_password
# AUTHENTIK_EMAIL__USE_TLS=true
# AUTHENTIK_EMAIL__FROM=authentik@yourdomain.com
EOF

Create the Docker Compose file:

cat > /opt/authentik/docker-compose.yml << 'EOF'
version: "3.8"

services:
  server:
    container_name: authentik_server
    image: ghcr.io/goauthentik/server:latest
    restart: always
    command: server
    environment:
      AUTHENTIK_REDIS__HOST: redis
      AUTHENTIK_POSTGRESQL__HOST: postgresql
      AUTHENTIK_POSTGRESQL__USER: authentik
      AUTHENTIK_POSTGRESQL__NAME: authentik
      AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
    env_file:
      - .env
    volumes:
      - ./media:/media
      - ./custom-templates:/templates
    ports:
      - "127.0.0.1:9000:9000"
      - "127.0.0.1:9443:9443"
    depends_on:
      - postgresql
      - redis

  worker:
    container_name: authentik_worker
    image: ghcr.io/goauthentik/server:latest
    restart: always
    command: worker
    environment:
      AUTHENTIK_REDIS__HOST: redis
      AUTHENTIK_POSTGRESQL__HOST: postgresql
      AUTHENTIK_POSTGRESQL__USER: authentik
      AUTHENTIK_POSTGRESQL__NAME: authentik
      AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
    env_file:
      - .env
    volumes:
      - ./media:/media
      - ./certs:/certs
      - ./custom-templates:/templates
    depends_on:
      - postgresql
      - redis

  postgresql:
    container_name: authentik_postgres
    image: docker.io/library/postgres:16-alpine
    restart: always
    volumes:
      - database:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: ${PG_PASS}
      POSTGRES_USER: authentik
      POSTGRES_DB: authentik
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -d authentik -U authentik"]
      interval: 10s
      timeout: 5s
      retries: 5

  redis:
    container_name: authentik_redis
    image: docker.io/library/redis:7-alpine
    command: --save 60 1 --loglevel warning
    restart: always
    volumes:
      - redis:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  database:
  redis:
EOF

Start the stack:

cd /opt/authentik && docker compose up -d

Wait 30–60 seconds for initial database migrations, then verify all containers are running:

docker compose ps

Nginx Reverse Proxy with SSL

Configure Nginx to proxy requests to Authentik. Follow our Nginx reverse proxy guide and SSL certificate guide for the foundation:

cat > /etc/nginx/sites-available/authentik << 'EOF'
server {
    listen 80;
    server_name auth.yourdomain.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name auth.yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/auth.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/auth.yourdomain.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:9000;
        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;

        # WebSocket support
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}
EOF

ln -sf /etc/nginx/sites-available/authentik /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx

Initial Setup — Admin Account and First Tenant

Navigate to https://auth.yourdomain.com/if/flow/initial-setup/ to create the initial admin account. This is only available on first access — once an admin account exists, this URL is disabled.

After logging in, you will see the Authentik admin interface. The key concepts to understand:

Integration Pattern 1: OIDC/OAuth2 for Native SSO Apps

Many self-hosted applications support OIDC (OpenID Connect) or OAuth2 natively. This is the cleanest integration — users click "Login with Authentik" and are redirected to a centralized login page.

Here is how to connect Gitea as an example:

In Authentik admin:

  1. Go to Applications, then Providers, then Create
  2. Select "OAuth2/OpenID Provider"
  3. Name: "Gitea"
  4. Authorization flow: default-provider-authorization-implicit-consent
  5. Client ID: (auto-generated, copy this)
  6. Client Secret: (auto-generated, copy this)
  7. Redirect URI: https://git.yourdomain.com/user/oauth2/authentik/callback
  8. Save, then create an Application pointing to this provider

In Gitea admin (Site Administration, Authentication Sources, Add):

Authentication Type: OAuth2
Provider: OpenID Connect
Client ID: (from Authentik)
Client Secret: (from Authentik)
OpenID Connect Auto Discovery URL: https://auth.yourdomain.com/application/o/gitea/.well-known/openid-configuration

The same pattern works for Grafana, Portainer, Outline, BookStack, and dozens of other self-hosted applications. Each application needs its own provider in Authentik with the correct redirect URI.

For Grafana specifically, add these environment variables to its Docker Compose configuration:

environment:
  GF_AUTH_GENERIC_OAUTH_ENABLED: "true"
  GF_AUTH_GENERIC_OAUTH_NAME: "Authentik"
  GF_AUTH_GENERIC_OAUTH_CLIENT_ID: "your-client-id"
  GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET: "your-client-secret"
  GF_AUTH_GENERIC_OAUTH_SCOPES: "openid profile email"
  GF_AUTH_GENERIC_OAUTH_AUTH_URL: "https://auth.yourdomain.com/application/o/authorize/"
  GF_AUTH_GENERIC_OAUTH_TOKEN_URL: "https://auth.yourdomain.com/application/o/token/"
  GF_AUTH_GENERIC_OAUTH_API_URL: "https://auth.yourdomain.com/application/o/userinfo/"
  GF_AUTH_SIGNOUT_REDIRECT_URL: "https://auth.yourdomain.com/application/o/grafana/end-session/"

Integration Pattern 2: Forward Auth for Apps Without SSO

Many self-hosted applications do not support OAuth2 or any external authentication. For these, Authentik's forward auth integration places an authentication layer in front of the application at the Nginx level — users must authenticate through Authentik before Nginx allows the request through to the backend.

In Authentik admin:

  1. Go to Applications, then Providers, then Create
  2. Select "Proxy Provider"
  3. Name: "App Forward Auth"
  4. Authorization flow: default-provider-authorization-implicit-consent
  5. Mode: Forward auth (single application)
  6. External host: https://app.yourdomain.com
  7. Save, then create an Application pointing to this provider

In your Nginx site configuration:

server {
    listen 443 ssl http2;
    server_name app.yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/app.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/app.yourdomain.com/privkey.pem;

    # Authentik forward auth
    location /outpost.goauthentik.io {
        internal;
        proxy_pass http://127.0.0.1:9000/outpost.goauthentik.io;
        proxy_set_header Host $host;
        proxy_set_header X-Original-URL $scheme://$http_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;
    }

    location / {
        auth_request /outpost.goauthentik.io;

        # Forward authentication headers to the backend
        auth_request_set $authentik_username $upstream_http_x_authentik_username;
        auth_request_set $authentik_groups $upstream_http_x_authentik_groups;
        auth_request_set $authentik_email $upstream_http_x_authentik_email;
        proxy_set_header X-authentik-username $authentik_username;
        proxy_set_header X-authentik-groups $authentik_groups;
        proxy_set_header X-authentik-email $authentik_email;

        # Redirect to Authentik login on 401
        error_page 401 = @authentik_proxy_signin;

        proxy_pass http://127.0.0.1:YOUR_APP_PORT;
        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;
    }

    location @authentik_proxy_signin {
        internal;
        add_header Set-Cookie "rd=$scheme://$http_host$request_uri; Path=/; HttpOnly; Secure";
        return 302 https://auth.yourdomain.com/outpost.goauthentik.io/start?rd=$scheme://$http_host$request_uri;
    }
}

With forward auth, any unauthenticated request to the protected application gets redirected to Authentik's login page. After successful authentication, the user is redirected back to the original URL with access granted.

Integration Pattern 3: LDAP Provider for Legacy Applications

Some applications only support LDAP authentication (older software, some network equipment, certain enterprise tools). Authentik can present itself as an LDAP server:

  1. In Authentik admin, create an LDAP Provider under Applications, then Providers
  2. Configure the Base DN (e.g., dc=yourdomain,dc=com)
  3. Create an LDAP Outpost under Applications, then Outposts that uses this provider
  4. The outpost exposes LDAP on port 389 (or 636 for LDAPS) inside Docker

Applications that support LDAP can then authenticate against Authentik using standard LDAP bind operations. Users and groups defined in Authentik appear as LDAP entries.

Adding Users and Groups

Manage users and groups through the Authentik admin interface under Directory:

# Users can also be created via the Authentik API
curl -X POST https://auth.yourdomain.com/api/v3/core/users/ \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "username": "partner",
    "name": "Partner Name",
    "email": "partner@yourdomain.com",
    "is_active": true
  }'

Groups provide role-based access control. Common groups for a self-hosted environment:

In each Application's configuration, you can restrict access to specific groups. For example, allow only the "admins" group to access Portainer, while the "family" group can access Immich and Jellyfin.

Multi-Factor Authentication

Authentik supports multiple MFA methods, and because authentication is centralized, enabling MFA in Authentik protects every connected application simultaneously:

TOTP (Time-based One-Time Passwords) — compatible with Google Authenticator, Authy, Bitwarden, and any other TOTP app. Users enroll by scanning a QR code.

WebAuthn / FIDO2 — hardware security keys (YubiKey, SoloKey) and platform authenticators (Touch ID, Windows Hello, Android fingerprint). The strongest MFA option available.

To enforce MFA for all users, modify the default authentication flow:

  1. Go to Flows and Stages, then Flows, then default-authentication-flow
  2. Add an "Authenticator Validation Stage" after the password stage
  3. Configure it to require at least one of: TOTP, WebAuthn, or static recovery tokens
  4. Add an "Authenticator Setup Stage" for users who have not enrolled in MFA yet

Once configured, users who have not set up MFA will be prompted to enroll during their next login. Users who have MFA configured will be prompted for their second factor after entering their password.

Connecting Your Self-Hosted Apps

Here is a practical checklist for connecting common self-hosted applications to Authentik:

When handling authentication for 10+ services, every login triggers OIDC token generation, session management, and group membership checks. A Dedicated VPS ensures dedicated resources handle auth requests instantly — because a slow identity provider means slow logins across every application in your stack. When authentication is the gateway to everything, latency is unacceptable.

Your Identity Provider Needs 100% Uptime

Your identity provider is the single most critical service in your self-hosted stack. If Authentik goes down, nobody can log into anything — every application behind SSO becomes inaccessible. This is not like a media server going down where the impact is inconvenience. An identity provider outage is a total infrastructure lockout.

MassiveGRID's Proxmox HA clustering provides automatic failover at the hypervisor level. If the physical node running your VPS experiences a hardware failure, your VM is automatically restarted on a healthy node — typically within seconds. Combined with Ceph's triple-replicated storage that ensures your database is intact after failover, your Authentik instance achieves genuine high availability without manual intervention.

The 100% uptime SLA means your authentication infrastructure stays available even during maintenance windows, hardware replacements, and unexpected failures.

Backup Strategy for the Identity Database

The Authentik database contains user accounts, credentials, MFA enrollments, OAuth2 client secrets, group memberships, and flow configurations. Losing this data means reconfiguring every application integration from scratch and requiring every user to re-enroll. Back it up rigorously:

#!/bin/bash
# /opt/authentik/backup.sh — run nightly via cron
BACKUP_DATE=$(date +%Y-%m-%d)
BACKUP_DIR="/backup/authentik"
mkdir -p ${BACKUP_DIR}

# Backup PostgreSQL database
docker exec authentik_postgres pg_dump -U authentik authentik | gzip > ${BACKUP_DIR}/authentik-db-${BACKUP_DATE}.sql.gz

# Backup media directory (custom branding, icons)
tar czf ${BACKUP_DIR}/authentik-media-${BACKUP_DATE}.tar.gz -C /opt/authentik media/

# Backup environment file (contains secret key — encrypt this!)
gpg --symmetric --cipher-algo AES256 -o ${BACKUP_DIR}/authentik-env-${BACKUP_DATE}.gpg /opt/authentik/.env

# Retain 30 days of backups (identity data is critical)
find ${BACKUP_DIR} -name "authentik-*" -mtime +30 -delete

echo "Authentik backup completed: ${BACKUP_DATE}"

Test your restore procedure periodically. Spin up a test instance, restore the database backup, and verify that user accounts, MFA enrollments, and application integrations are intact. A backup that has never been tested is not a backup.

Prefer Managed Identity Infrastructure?

Your identity provider is the master key to your entire self-hosted infrastructure. A vulnerability in the authentication layer compromises everything behind it. Security patches must be applied within hours of release, not days. Backups must be verified automatically, not manually. SSL certificates must renew without fail.

With MassiveGRID's fully managed hosting, our team handles the security-critical maintenance that an identity provider demands — patching, backup verification, SSL management, intrusion monitoring, and 24/7 incident response. Your authentication infrastructure gets the same operational rigor as a commercial identity service, with the privacy and control of self-hosting. When the security of every application depends on one service, managed infrastructure is not a luxury — it is risk management.