Why choose ZITADEL over Authentik or Keycloak?
ZITADEL occupies a precise niche among open-source authentication platforms. Unlike Authelia (an authentication proxy without its own user management) or Authentik (a feature-rich full-stack IdP), it targets development teams that want enterprise-grade IAM from day one: native multi-tenancy, complete gRPC + REST APIs, and an official Terraform provider (zitadel/zitadel on the Terraform registry).
Be careful with memory figures. The operations documentation says ZITADEL itself needs about 512 MB of RAM and less than one CPU core, database excluded, with CPU spikes during password hashing. The previous version of this article claimed "under 100 MB": that figure is not in the documentation, so do not size your VPS on it. The resource comparison with Keycloak is not quantified here because no sourced measurement exists.
Sized prerequisites
- VPS: 2 vCPU and 2 GB of RAM (the upstream Docker Compose guide asks for at least 2 GB), Ubuntu 24.04. Plan for more as the database grows: upstream counts about 4 GB of RAM per database core.
- Docker: Docker Engine 24 or later with Compose v2; the legacy
docker-compose(v1) binary is not supported. - PostgreSQL: versions 14 to 18 are supported. The ServOrbit template deploys PostgreSQL 16.
- Ports: ZITADEL listens internally on
8080. Only443should be public (plus80if you let Let's Encrypt issue the certificate). The template binds8080to127.0.0.1and lets nginx serve the outside. - Domain: a name or subdomain pointing to the VPS IP, for example
auth.your-domain.com, over HTTPS. - Masterkey:
ZITADEL_MASTERKEYmust be exactly 32 characters and cannot change after initialization without losing access to encrypted data. The template generates it; back it up off the VPS.
Install ZITADEL from the Marketplace
Installation in 3 steps
Create the VPS
From your ServOrbit client area, choose the VPS range and click Configure. In the Application field, select ZITADEL. Enter your domain (e.g.
auth.your-domain.com).If you do not have a domain, the free ServOrbit subdomain is available immediately, HTTPS included.
Automatic startup
The installation script generates the
MASTERKEY, the PostgreSQL password and the admin password, then launches PostgreSQL 16 and ZITADEL via Docker Compose. Typical duration: 2 to 4 minutes.Access the console
Once installation is complete, your credentials are displayed in the client area.
- URL:https://<your-domain>/ui/console
- Username:admin. If the page rejects it, tryadmin@zitadel.<your-domain>: ZITADEL builds login names as [email protected], as in upstream's[email protected]example.
- Password: the generated oneLog in and change it immediately.
What the template deploys
The template is a Docker Compose file with two services: db (PostgreSQL 16) and zitadel, started with start-from-init --tlsMode disabled --masterkeyFromEnv. TLS is therefore terminated by nginx, not by ZITADEL. The image followed is the stable tag of ghcr.io/zitadel/zitadel, which means the version actually running depends on the day the image was pulled: check it with docker compose images (see the section on updates).
The domain is passed to ZITADEL_EXTERNALDOMAIN, with ZITADEL_EXTERNALPORT=443 and ZITADEL_EXTERNALSECURE=true. The template treats the domain as fixed at first boot: pick the right one from the start.
External domain, TLS and reverse proxy
- Three values to align —
ZITADEL_EXTERNALDOMAIN,ZITADEL_EXTERNALPORTandZITADEL_EXTERNALSECUREmust describe the real public URL. ZITADEL only serves requests sent to the expected protocol, host and port; otherwise you get "Instance not found". - Changing domain — upstream documents that these options can be changed at any time, provided you rerun the setup phase. So it is not impossible, as the previous version claimed, but it is not a trivial operation: every client application references the old URL.
- Host preserved — the reverse proxy must forward the original
Hostheader andX-Forwarded-Proto: https. The upstream nginx example terminates TLS, then talks to ZITADEL over unencrypted HTTP/2 (h2c) withgrpc_pass. - End-to-end HTTP/2 — the console requires HTTP/2 all the way to ZITADEL. The proxies upstream tested are Traefik v3, NGINX 1.x stable, Caddy v2 and Apache httpd 2.4.
- CDN or WAF in front — the documentation states that a proxy, WAF or CDN placed in front of ZITADEL must also support HTTP/2. Test the console after any DNS switch to a proxied mode.
Create your first OIDC application
Once logged in to the ZITADEL console:
1. Go to Projects then Create
2. Name your project (e.g. My App)
3. Click Add Application
4. Choose the type: Web (server-side apps), Native (mobile/desktop) or API (machine-to-machine)
5. For a Web app, choose PKCE (recommended) and enter your Redirect URIs
ZITADEL generates the Client ID. Note the OIDC discovery endpoint: https://<your-domain>/.well-known/openid-configuration.
Integrate ZITADEL with your self-hosted applications
Any application that speaks OIDC can rely on ZITADEL: you only need the Client ID, the secret if any, and the discovery endpoint. Common examples:
- Gitea: Site Administration, Authentication, add an OAuth2 source of type OpenID Connect, then enter Client ID, secret and discovery endpoint
- Grafana:
[auth.generic_oauth]section ofgrafana.iniwith ZITADEL's authorization, token and user info URLs - Nextcloud:
Social LoginorOpenID Connect user backendapp - Any other OIDC application: enter the discovery endpoint, the Client ID and the Redirect URIs declared in the project; check the application's documentation for the exact settings
Multi-tenant management: organizations and projects
ZITADEL relies on a native multi-tenant model: you create several Organizations (for example one per customer of your SaaS), each with its own users and login policies. Each organization can have its own Projects and Roles.
Note that roles are not added to tokens automatically, contrary to what the previous version claimed. You must tick Assert Roles on Authentication in the project to get them through userinfo, or User Roles Inside ID Token in the application, and request the role scope urn:zitadel:iam:org:project:{projectId}:roles. Also add urn:zitadel:iam:org:project:id:{projectId}:aud if you get "Invalid audience".
Tip: enable passkeys from the start
In the instance settings, enable Passkey/WebAuthn authentication. Your users sign in without a password from their browser.
Domain discovery: what should work
Domain discovery routes a user to the right organization based on the domain of their email address. In the scenario described upstream, a user at [email protected] is redirected to their company's identity provider, a user at [email protected] sees a password-and-MFA login page, and an unknown domain falls into the default organization.
The conditions described in the upstream guide are:
- Instance — under Login Behavior and Security, the "Domain discovery allowed" option is enabled
- Default organization — it receives all users without a match
- Verified domain — each domain is verified in the organization's Organization Domains, by DNS TXT record or HTTP challenge; keep the DNS record, because ZITADEL re-checks periodically
- Organization login policy — "External IDP allowed" enabled, and for a purely federated organization "Username Password allowed" disabled
- Identity provider — configured and attached to that organization; with a single provider the redirect is automatic, with several the user chooses
- Email sign-in — the login name entered must be able to be an email address whose domain matches
Known limits: zitadel/zitadel issue #10662
On self-hosted ZITADEL v4.1.2, domain discovery does not work for some deployments despite organizations with a verified domain and a configured identity provider. Issue #10662 ("Domain Discovery doesn't work") was opened in September 2025 and is still open as this article is written. The table separates what the team or the documentation establishes from what users report.
Issue #10662: confirmed and reported
Scroll the table
| Point | Confirmed (team or documentation) | Reported by users, not confirmed |
|---|---|---|
| Symptom | The issue is open, set to medium priority in October 2026, with the labels area/console, area/loginV1 and ng-v4-legacy | "User not found in the system" message for an address on the verified domain, on v4.1.2, identical on OKD and on Docker |
| Announced fix | A maintainer said in October 2025 that PR #10729 fixed it, then the issue was reopened | The defect persists on v4.3.1, then on Login V1 on v4.5.0, v4.6.3, v4.7.0 and v4.11.0 |
| Cause | No cause is publicly established | No verified hypothesis |
| Scope | Sub-issues exist (status on 16 July 2026: one done, two in progress, one not started), attached to the parent effort #12337 | Reproduced on Login V2 (initial report) then on Login V1; the case of several domains in one organization remains unanswered |
| Console | On 27 July 2026 a contributor says the Console is to be deprecated within 12 months and that no fix is prioritized until then; the issue is kept open | Nothing to report |
| Workaround | No workaround is documented in the thread | None has been proven by participants |
In practice: do not build an enterprise SSO architecture on domain discovery alone while this issue is open. Plan a second path (a per-provider sign-in link, see below) and test the routing with a real user before going live. On 27 October 2026 a team member asked for the issue's status to be verified: follow the issue rather than this article for its outcome.
Troubleshooting SSO sign-in and the verified domain
Diagnosis in 5 steps
Check the instance
In the instance settings, Login Behavior and Security section, check that "Domain discovery allowed" is enabled. Without it no organization rule is consulted.
Check the domain
In the target organization, open Organization Domains: the domain must have the verified status. If verification fails, restore the TXT record or the requested HTTP challenge, and never delete the record afterwards.
Check the login policy
In the same organization, "External IDP allowed" must be enabled and the identity provider active. If "Username Password allowed" stays enabled, the user may land on the password prompt instead of being redirected, a symptom described in the issue.
Test the login name
In a private window, enter a full address whose domain is exactly the verified one. A typo in the domain, a subdomain, or a user who already exists in another organization changes the outcome.
Bypass discovery
If everything is compliant and "User not found in the system" persists, you are probably in the case of issue #10662. The scope
urn:zitadel:iam:org:idp:id:<idp_id>redirects the user straight to the chosen identity provider: the documentation describes it, but it has not been proven on this defect.
- "User not found in the system": apply the five steps above; if they are compliant, it is the defect in the issue
- "Instance not found" (404): the host, port and protocol received do not match
ZITADEL_EXTERNALDOMAIN,ZITADEL_EXTERNALPORTandZITADEL_EXTERNALSECURE; check that nginx keeps theHostheader - Blank console or failing API calls behind a proxy or CDN: check HTTP/2 end to end
adminusername rejected: try theadmin@zitadel.<your-domain>form given above- Domain verification failing: the upstream guide notes that the ACME verification challenge can be disabled in the default settings, but only do so knowingly
Updates and security
- Current version — as this is written, the latest stable release is v4.20.0, published in October 2026; read the release notes before every upgrade
- Advisory GHSA-jh92-5mrj-p2w2 — account takeover through predictable session IDs in Login V2 (high severity, no CVE identifier listed). Versions 4.0.0 to 4.19.1 are affected, fixed in 4.19.2; a deployment using only Login V1 is not affected
- Session cookie secret — the fix asks for
ZITADEL_SESSION_COOKIE_SECRET(at least 32 characters, identical on all replicas); users must sign in again after the upgrade - 3.x branch — it reached end of life on 31 August 2026 and will not get a fix: move to a recent 4.x
Before updating
Back up the database with pg_dump and the masterkey, then run docker compose pull followed by docker compose up -d from the Compose folder. Since the template follows the stable tag, check the running version afterwards with docker compose images.
Install ZITADEL via the ServOrbit Marketplace
You can install ZITADEL directly from the [ServOrbit Marketplace](/marketplace/security/zitadel): VPS pre-configured with Docker Compose, nginx, HTTPS and automatically generated secrets. The template is provided as a starting point, with no support for your identity configuration.