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:
| Feature | Authentik | Keycloak |
|---|---|---|
| Stack | Python/Django + Go proxy | Java/Quarkus |
| Minimum RAM | ~1 GB | ~2 GB |
| Admin UI | Modern, intuitive | Feature-rich but complex |
| Forward Auth | Built-in for Traefik/Nginx | Requires plugin |
| Flow Builder | Visual drag-and-drop | Config 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
- VPS: Ubuntu 22.04 LTS — minimum 2 GB RAM (4 GB recommended for production)
- CPU: 1 vCPU minimum (2 vCPU recommended)
- Docker Engine 24+ and Docker Compose v2
- A domain pointing to your VPS IP (e.g.,
auth.example.com) - Ports 80 and 443 open in your firewall
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
Step 5: Initial Setup
Open https://auth.example.com/if/flow/initial-setup/ in your browser. You'll see a first-time setup page:
- Enter your desired admin Email and Password
- Click Continue — Authentik creates the admin user automatically
- 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:
- Go to Admin Interface → Applications → Providers → Create
- Choose OAuth2/OpenID Provider
- Name the provider and select an Authorization flow (use default:
default-provider-authorization-explicit-consent) - Enter the Redirect URIs of your application (e.g.,
https://app.example.com/callback) - Save — copy the Client ID and Client Secret
- 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
/-/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"