Back to articles
Technology Insight

Centralizing Identity Management: A Guide to Deploying Zitadel SSO on a VPS for Self-Hosted Applications

May 29, 2026

Introduction to Modern Identity Access Management

As organizations expand their digital footprints, managing user credentials across an array of self-hosted applications becomes a significant operational hurdle. Relying on isolated, application-specific authentication databases introduces fragmented user experiences, heightened security liabilities, and administrative friction. To mitigate these risks, enterprise IT architectures depend on Identity and Access Management (IAM) systems providing Single Sign-On (SSO).

While legacy tools like Keycloak have long served as open-source standards, modern software development requires lightweight, API-first solutions built for cloud-native deployment. Zitadel addresses this need as a modern, high-performance IAM platform written in Go, featuring built-in multi-tenancy and an event-sourced architecture. This comprehensive technical guide provides a step-by-step framework to deploy and configure a self-hosted Zitadel instance on a Virtual Private Server (VPS), enabling unified authentication across your entire self-hosted application ecosystem.

---

Why Choose Zitadel for Self-Hosted Infrastructure?

Selecting the appropriate IAM solution involves balancing resource constraints against required features. Zitadel provides several key advantages for self-hosted environments:

  • Event-Sourced Architecture: Every configuration change, login attempt, or credential update is preserved as an immutable event, providing an enterprise-grade audit trail for strict compliance tracking (e.g., SOC 2 or GDPR).
  • Resource Efficiency: Unlike Java-based legacy alternatives, Zitadel's Go runtime operates with minimal memory overhead, making it highly efficient for budget-friendly VPS configurations.
  • Native Multi-Tenancy: It isolates organizations, projects, and users out-of-the-box, allowing a single VPS instance to safely serve multiple business units or external clients.
  • Modern Security Standards: Out-of-the-box support for OpenID Connect (OIDC), OAuth 2.0, SAML 2.0, and phishing-resistant FIDO2 Passkeys.
---

Prerequisites and System Requirements

Before beginning the installation, ensure your environment meets the following specifications:

1. VPS Hardware Resources

  • CPU: Minimum 1 vCPU (2 vCPUs recommended for efficient password hashing performance).
  • RAM: Minimum 2 GB available RAM.
  • Storage: 20 GB NVMe or SSD storage with sufficient overhead for database logs.
  • OS: A modern Linux distribution (Ubuntu 22.04 LTS or Debian 12 recommended).

2. Network and Domain Settings

  • A fully qualified domain name (FQDN) pointed to your VPS public IP address via an A record (e.g., auth.yourdomain.com).
  • Open network ports: 80 (HTTP validation) and 443 (HTTPS/gRPC traffic).
---

Step 1: Preparing the VPS Environment

Connect to your VPS via SSH and update your system package repositories to the latest versions:

sudo apt update && sudo apt upgrade -y

Next, install the required dependencies: Docker Engine and the Docker Compose plugin. These components isolate the Zitadel core application and its backing storage layer.

sudo apt install -y curl git secure-delete
curl -fsSL [https://get.docker.com](https://get.docker.com) -o get-docker.sh
sudo sh get-docker.sh

Verify that your Docker environment is fully operational before moving forward:

docker compose version && docker info
---

Step 2: Designing the Docker Compose Configuration

To ensure system stability, utilize a decoupled architecture containing a PostgreSQL 16 database service and the Zitadel runtime binary. Create a dedicated project directory and fetch the necessary configuration blueprints:

mkdir -p ~/zitadel-sso && cd ~/zitadel-sso

Create a docker-compose.yml file containing the following multi-container definitions:

version: "3.8"

services:
  zitadel-db:
    image: postgres:16-alpine
    container_name: zitadel-db
    environment:
      POSTGRES_DB: zitadel
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  zitadel:
    image: ghcr.io/zitadel/zitadel:stable
    container_name: zitadel-api
    command: start-from-init --masterkey "${ZITADEL_MASTERKEY}" --tlsMode disabled
    ports:
      - "8080:8080"
    environment:
      - ZITADEL_DATABASE_POSTGRES_HOST=zitadel-db
      - ZITADEL_DATABASE_POSTGRES_PORT=5432
      - ZITADEL_DATABASE_POSTGRES_DATABASE=zitadel
      - ZITADEL_DATABASE_POSTGRES_USER_WITH_PRIVILEGES=postgres
      - ZITADEL_DATABASE_POSTGRES_PASSWORD_WITH_PRIVILEGES=${DATABASE_PASSWORD}
      - ZITADEL_EXTERNALDOMAIN=${ZITADEL_DOMAIN}
      - ZITADEL_EXTERNALPORT=443
      - ZITADEL_EXTERNALSECURE=true
    depends_on:
      zitadel-db:
        condition: service_healthy
    restart: unless-stopped

volumes:
  pgdata:
    driver: local

Note: We disable Zitadel's internal TLS layer within the container definition because SSL termination and HTTP/2 routing will be managed at the edge by a reverse proxy.

---

Step 3: Managing Secrets and Initialization

Zitadel requires secure variables, including a unique 32-character master key used for internal database encryption layers. Generate these values using cryptographically secure commands and store them in an .env file:

# Generate a 32-character master key
ZITADEL_MASTERKEY=$(tr -dc A-Za-z0-9 # Generate a strong database password
DB_PASS=$(tr -dc A-Za-z0-9
echo "ZITADEL_MASTERKEY=${ZITADEL_MASTERKEY}" >> .env
echo "DATABASE_PASSWORD=${DB_PASS}" >> .env
echo "ZITADEL_DOMAIN=auth.yourdomain.com" >> .env

Launch the containers in detached mode. The start-from-init flag instructs Zitadel to execute schema migrations automatically upon detecting an empty database:

docker compose up -d

Monitor the initialization logs to verify successful system creation and retrieve your temporary administrative credentials:

docker compose logs -f zitadel

Look for log outputs specifying the initial instance setup status. The default administrative username is typically [email protected] or as specified during bootstrap routines.

---

Step 4: Configuring Reverse Proxy and SSL Termination

To secure transmission vectors and support Zitadel's administrative dashboard requirements, install Nginx to handle incoming HTTPS and end-to-end gRPC over HTTP/2 connections.

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

Obtain a free, trusted Let's Encrypt TLS certificate for your authentication domain:

sudo certbot --nginx -d auth.yourdomain.com

Modify your Nginx configuration block located at /etc/nginx/sites-available/zitadel to handle standard and gRPC traffic proxy requests:

server {
    server_name auth.yourdomain.com;

    listen 443 ssl http2;
    ssl_certificate /etc/letsencrypt/live/[auth.yourdomain.com/fullchain.pem](https://auth.yourdomain.com/fullchain.pem);
    ssl_certificate_key /etc/letsencrypt/live/[auth.yourdomain.com/privkey.pem](https://auth.yourdomain.com/privkey.pem);

    # Required for handling large identity tokens
    proxy_buffer_size   128k;
    proxy_buffers       4 256k;
    proxy_busy_buffers_size 256k;

    location / {
        proxy_pass [http://127.0.0.1:8080](http://127.0.0.1:8080);
        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;

        # Enable HTTP/2 features required by the console
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

Link the configuration file and restart the Nginx daemon to apply changes:

sudo ln -s /etc/nginx/sites-available/zitadel /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl restart nginx
---

Step 5: Setting Up Your First Self-Hosted App Application

With Zitadel running at [https://auth.yourdomain.com](https://auth.yourdomain.com), you can now connect your self-hosted web applications (such as Nextcloud, Gitea, or custom internal management dashboards) using OpenID Connect (OIDC).

  1. Log into the Console: Navigate to your domain, authenticate using the root credentials, and immediately change the default password.
  2. Create a Project: In the left navigation panel, go to Projects, click Create New Project, and give it an identifying name (e.g., Internal Core Stack).
  3. Register an Application: Inside your newly created project scope, click New Application. Enter your app name, then choose Web Application or User Agent / SPA based on your architecture.
  4. Select Authentication Method: Choose Code (Authorization Code Flow with PKCE), which is the standard, secure approach for modern web apps.
  5. Define Redirect URIs: Provide the exact sign-in callback endpoint for your target app. For example, if integrating Gitea, add:
    [https://git.yourdomain.com/user/oauth2/Zitadel/callback](https://git.yourdomain.com/user/oauth2/Zitadel/callback)

Upon completing the registration wizard, Zitadel will display your Client ID and Client Secret. Copy these values immediately to configure your application's authentication settings.

---

Conclusion and Maintenance Operations

You have successfully established a secure, centralized Single Sign-On (SSO) authentication center on your VPS using Zitadel. Your self-hosted applications can now offload user authentication, identity federation, and multi-factor security rules to a hardened control plane.To maintain long-term operational health, implement automated database backup routines. You can export your entire state to an encrypted storage volume using native utility extensions:

docker exec -t zitadel-db pg_dump -U postgres zitadel > ~/backups/zitadel_$(date +%F).sql

By shifting to an integrated IAM model, you reduce identity fragmentation and strengthen your infrastructure against unauthorized access, laying a secure foundation for your self-hosted architecture.

Centralizing Identity Management: A Guide to Deploying Zitadel SSO on a VPS for Self-Hosted Applications | DPTCloud