Install Authentik SSO Identity Provider on VPS Security

Install Authentik on VPS — Self-Hosted SSO Identity Provider

Authentik is an open-source Identity Provider (IdP) that supports SSO, OAuth2, OIDC, SAML2, and LDAP — think of it as a self-hosted alternative to Okta or Auth0. You run it on your own VPS, giving you complete control over authentication without relying on third-party cloud services.

This guide walks you through installing Authentik on Ubuntu 22.04 VPS using Docker Compose with Nginx as a reverse proxy and Let's Encrypt SSL. The whole setup takes about 30 minutes.

Authentik vs Keycloak — Which One to Choose?

Both are popular self-hosted IdPs, but they differ in meaningful ways:

FeatureAuthentikKeycloak
StackPython/Django + Go proxyJava/Quarkus
Minimum RAM~1 GB~2 GB
Admin UIModern, intuitiveFeature-rich but complex
Forward AuthBuilt-in for Traefik/NginxRequires plugin
Flow BuilderVisual drag-and-dropConfig files

Authentik is a great fit for small to mid-size teams that want fast setup and a clean UI. Keycloak excels in enterprise environments with complex federation requirements.

Requirements

Step 1: Install Docker

# Update packages and install Docker
sudo apt update && sudo apt upgrade -y
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
newgrp docker

# Verify installation
docker --version
docker compose version

Step 2: Create Docker Compose Configuration

Create the directory and generate secure credentials:

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

# Generate secure random secrets
echo "PG_PASS=$(openssl rand -base64 36 | tr -dc 'a-zA-Z0-9' | head -c 36)" >> .env
echo "AUTHENTIK_SECRET_KEY=$(openssl rand -base64 60 | tr -dc 'a-zA-Z0-9' | head -c 60)" >> .env
echo "AUTHENTIK_ERROR_REPORTING__ENABLED=false" >> .env

cat .env

Create the docker-compose.yml file:

cat > /opt/authentik/docker-compose.yml << 'EOF'
services:
  postgresql:
    image: docker.io/library/postgres:16-alpine
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
      interval: 30s
      timeout: 5s
      retries: 5
    volumes:
      - database:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: ${PG_PASS}
      POSTGRES_USER: authentik
      POSTGRES_DB: authentik

  redis:
    image: docker.io/library/redis:alpine
    command: --save 60 1 --loglevel warning
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
      interval: 30s
      timeout: 3s
      retries: 5
    volumes:
      - redis:/data

  server:
    image: ghcr.io/goauthentik/server:2024.12
    restart: unless-stopped
    command: server
    environment:
      AUTHENTIK_REDIS__HOST: redis
      AUTHENTIK_POSTGRESQL__HOST: postgresql
      AUTHENTIK_POSTGRESQL__USER: authentik
      AUTHENTIK_POSTGRESQL__NAME: authentik
      AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
      AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
      AUTHENTIK_ERROR_REPORTING__ENABLED: ${AUTHENTIK_ERROR_REPORTING__ENABLED}
    volumes:
      - ./media:/media
      - ./custom-templates:/templates
    ports:
      - "9000:9000"
      - "9443:9443"
    depends_on:
      postgresql:
        condition: service_healthy
      redis:
        condition: service_healthy

  worker:
    image: ghcr.io/goauthentik/server:2024.12
    restart: unless-stopped
    command: worker
    environment:
      AUTHENTIK_REDIS__HOST: redis
      AUTHENTIK_POSTGRESQL__HOST: postgresql
      AUTHENTIK_POSTGRESQL__USER: authentik
      AUTHENTIK_POSTGRESQL__NAME: authentik
      AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
      AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
      AUTHENTIK_ERROR_REPORTING__ENABLED: ${AUTHENTIK_ERROR_REPORTING__ENABLED}
    user: root
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./media:/media
      - ./certs:/certs
      - ./custom-templates:/templates
    depends_on:
      postgresql:
        condition: service_healthy
      redis:
        condition: service_healthy

volumes:
  database:
  redis:
EOF

Step 3: Configure Nginx Reverse Proxy

sudo apt install -y nginx certbot python3-certbot-nginx

sudo tee /etc/nginx/sites-available/authentik << 'EOF'
server {
    listen 80;
    server_name auth.example.com;
    return 301 https://$host$request_uri;
}

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

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

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers off;
    add_header Strict-Transport-Security "max-age=63072000" always;

    location / {
        proxy_pass http://127.0.0.1:9000;
        proxy_http_version 1.1;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Host $http_host;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}
EOF

sudo ln -s /etc/nginx/sites-available/authentik /etc/nginx/sites-enabled/
sudo nginx -t

Step 4: Obtain SSL and Start Authentik

# Get SSL certificate (replace with your domain and email)
sudo certbot certonly --nginx -d auth.example.com --email [email protected] --agree-tos -n

sudo systemctl reload nginx

# Pull images and start all services
cd /opt/authentik
docker compose pull
docker compose up -d

# Watch startup logs (takes 1-2 minutes)
docker compose logs -f server
Note: On first startup, Authentik needs 2–3 minutes to run database migrations. Wait until you see "Starting HTTP server" in the logs before opening your browser.

Step 5: Initial Setup

Open https://auth.example.com/if/flow/initial-setup/ in your browser. You'll see a first-time setup page:

  1. Enter your desired admin Email and Password
  2. Click Continue — Authentik creates the admin user automatically
  3. Log into the Admin Interface at https://auth.example.com/if/admin/

Step 6: Create an Application and Provider

Example: creating an OAuth2/OIDC Provider for a web application:

  1. Go to Admin Interface → Applications → Providers → Create
  2. Choose OAuth2/OpenID Provider
  3. Name the provider and select an Authorization flow (use default: default-provider-authorization-explicit-consent)
  4. Enter the Redirect URIs of your application (e.g., https://app.example.com/callback)
  5. Save — copy the Client ID and Client Secret
  6. Go to Applications → Create and link the provider you just created

Step 7: Forward Authentication with Nginx

Authentik supports forward authentication — protect any application that has no built-in auth:

# Create Nginx snippet for Authentik forward auth
sudo tee /etc/nginx/snippets/authentik-auth.conf << 'EOF'
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;
auth_request_set $authentik_username $upstream_http_x_authentik_username;
auth_request_set $authentik_groups $upstream_http_x_authentik_groups;
EOF

# Include this snippet in any server block you want to protect:
# include /etc/nginx/snippets/authentik-auth.conf;

Verify Authentik is Working Correctly

# Check all containers are running
docker compose -f /opt/authentik/docker-compose.yml ps

# Health checks
curl -s https://auth.example.com/-/health/live/ && echo " — OK"
curl -s https://auth.example.com/-/health/ready/ && echo " — Ready"

# Check Authentik version via API
curl -s https://auth.example.com/api/v3/root/config/ | python3 -m json.tool | grep version

# Validate configuration
docker compose -f /opt/authentik/docker-compose.yml exec server ak check_config
Tip: The /-/health/ready/ endpoint returns HTTP 200 only when Authentik is fully ready to serve requests — use this for load balancer health checks and uptime monitoring.

Common Errors and Fixes

Error: "CSRF verification failed. Request aborted."

This happens when Nginx does not forward the X-Forwarded-Proto header, causing Authentik's CSRF protection to detect a protocol mismatch.

# Ensure your Nginx location block includes all required headers
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;

# Also verify HTTPS is working correctly
sudo certbot --nginx -d auth.example.com
sudo nginx -t && sudo systemctl reload nginx

Error: "relation 'authentik_core_application' does not exist"

Database migrations haven't completed, or the PostgreSQL container wasn't healthy when the server started.

# Run migrations manually
docker compose -f /opt/authentik/docker-compose.yml exec server ak migrate

# Check migration output in logs
docker compose -f /opt/authentik/docker-compose.yml logs server | grep -i "migrat"

# Restart if needed
docker compose -f /opt/authentik/docker-compose.yml restart

Error: "No such file or directory: /certs/..."

The worker container requires the certs/ directory to exist with correct permissions.

mkdir -p /opt/authentik/certs /opt/authentik/media /opt/authentik/custom-templates
chown -R 1000:1000 /opt/authentik/media /opt/authentik/certs
docker compose -f /opt/authentik/docker-compose.yml up -d

Error: "Config value X is not set"

A required environment variable is missing or incorrectly formatted in the .env file.

# Verify .env is loaded correctly
cd /opt/authentik && docker compose config | grep -A 5 "environment:"

# Check .env contents
cat /opt/authentik/.env

# Ensure the secret key contains no special characters that break YAML
grep AUTHENTIK_SECRET_KEY /opt/authentik/.env

Authentik login loop — redirect keeps repeating

Usually caused by a cookie domain mismatch or missing WebSocket upgrade headers in Nginx.

# Validate configuration
docker compose -f /opt/authentik/docker-compose.yml exec server ak check_config

# Clear browser cookies and try in incognito mode

# Check Nginx config includes WebSocket upgrade headers
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

Frequently Asked Questions

What protocols does Authentik support?

Authentik supports OAuth2/OIDC, SAML2, LDAP (for applications that need directory-style auth), RADIUS, SCIM (for automated user provisioning), and forward authentication for Nginx, Traefik, and Caddy. This covers nearly every authentication use case for a homelab or small organization.

How much RAM does Authentik require?

For production, 2–4 GB RAM is recommended: the server container uses about 500 MB, the worker uses 300 MB, PostgreSQL uses 200–500 MB depending on load, and Redis uses very little. Running below 1 GB total available memory will cause slowdowns and potential crashes.

How do I upgrade Authentik to a newer version?

Update the image tag in docker-compose.yml, then pull and restart:

# Update the image tag to the new version
sed -i 's/server:2024.12/server:2025.2/g' /opt/authentik/docker-compose.yml

# Pull new images and restart
cd /opt/authentik
docker compose pull
docker compose up -d

# Watch migration logs
docker compose logs -f server | grep -i "migrat"