Containers

Keycloak vs authentik vs Zitadel vs Authelia: Tested on One VM

authentik spends 1.2 seconds of CPU on every password login. Keycloak spends 72 milliseconds on the same job. On a 2 vCPU server that is the difference between 1.8 and 26 logins per second, and no feature table will tell you that.

Original content from computingforgeeks.com - post 173202

This Keycloak vs authentik vs Zitadel vs Authelia comparison is built on numbers we measured. Each identity provider ran from the tested Docker Compose stack of its own guide in this series, on the same Ubuntu 26.04 VM, one stack at a time. We timed restarts and password logins, counted CPU per login, ran 10 parallel clients, stepped RAM down until each one broke, and upgraded each from an earlier release with a probe running. The feature comparison next to the numbers was checked against the current documentation of each project, not memory. Every number below comes from Keycloak 26.8.0, authentik 2026.8.3, Zitadel v4.19.4 and Authelia 4.39.28 with LLDAP 0.6.3, tested in October 2026.

Keycloak vs authentik vs Zitadel vs Authelia architecture diagram: Nginx terminates TLS on 443 for each stack; Keycloak runs one Java container with PostgreSQL, authentik a server and a worker with PostgreSQL, Zitadel an API and a Next.js login container with PostgreSQL, Authelia with LLDAP, PostgreSQL and Redis, with idle memory per container

The diagram shows what you sign up to run. Keycloak is one big Java process. authentik splits into a web server and a background worker. Zitadel puts its login pages in a separate Next.js container. Authelia needs a directory beside it, and LLDAP is the small one we used. All four keep their state in PostgreSQL.

Keycloak vs authentik vs Zitadel vs Authelia at a glance

MetricKeycloakauthentikZitadelAuthelia + LLDAP
Containers in the stack2334
Idle memory, all containers539 MiB715 MiB214 MiB70 MiB
Image size, docker compose images1.40 GB2.37 GB0.96 GB0.80 GB
Restart to ready (data kept)17.7 s12.9 s12.6 s14.2 s
Password check, median38 ms471 ms946 ms353 ms
CPU per scripted login72 ms1,187 ms997 ms195 ms
Logins per second, 10 parallel clients26.21.82.15.3
Lowest RAM that passed1024 MiB1280 MiB768 MiB768 MiB
First RAM step that failed768 MiB1024 MiB512 MiB512 MiB
Upgrade outage, earlier release to current10.7 s40.8 s6.6 s4.1 s

Lower is better everywhere except logins per second. The upgrade row is how long the health or discovery URL of the login service failed during the upgrade, measured from outside through Nginx.

How we tested

One VM, cloned from the Ubuntu 26.04 template on a Proxmox node with an Intel Core i7-1165G7: 2 vCPU (host CPU type), 4 GB RAM with the balloon fixed at the same size, 40 GB disk, and Docker CE from the Docker repository (29.8.2 with Compose 5.6.0). Only one stack ran at a time, and the others were removed with docker compose down before the next one started. Nginx 1.28 on the host terminated TLS for all four with the same self-signed certificate, using the vhost from each guide. In production each guide uses Let’s Encrypt, and the Nginx with Let’s Encrypt guide for Ubuntu 26.04 covers that part.

Each stack is the core setup from its guide, with the hostnames changed to example.com names: the Keycloak production setup in Docker with one node instead of two, the authentik Docker Compose guide without the LDAP outpost, the Zitadel Docker Compose guide, and the Authelia and LLDAP guide. One test user was created through the API of each product. Password hashing stayed at the default of each product.

MetricHow it was measured
Idle memorydocker stats 10 minutes after the last restart, summed per stack. MemTotal minus MemAvailable inside the guest moved in step: 618 MiB with no stack, then +584, +756, +273 and +160 MiB
Restart to readydocker compose down and up -d with data kept, until every ready URL returned 200 through Nginx. Three runs each, all within 0.1 s, median shown
Password checkWall time of the single HTTP request that verifies the password, 30 logins in a row after one warm-up
CPU per loginCPU time of all containers in the stack from cgroup cpu.stat, divided by the 30 logins. It covers every request of the scripted login, listed below
Logins per second60 complete logins spread over 10 threads, all 60 succeeded on every stack
RAM floorVM memory lowered step by step with a reboot each time. A step passed when the stack came back on its own, 5 logins worked, and 5 more worked 60 seconds later
Upgrade outageA probe requested the health or discovery URL every 0.5 s through Nginx while the upgrade commands from the guide ran. Images were pulled before the stop, as in the guides

The scripted login is the shortest real path each product offers:

  • Keycloak: the authorization request to a realm, then the POST of the login form, which returns the redirect with a code.
  • authentik: the default authentication flow through its flow executor API, with the identification stage and then the password stage.
  • Zitadel: one call to the Session API with a password check, which is what its login UI does on the server side.
  • Authelia: one POST to /api/firstfactor, which binds to LLDAP with the password of the user.

So the CPU column is a fair price per login, but not an equal number of requests. Keycloak does two requests and authentik four, while Zitadel and Authelia do one each.

Login cost decides how many CPUs you need

The password hash explains almost all of the gap. Each product picks a deliberately slow algorithm, and the CPU cost per login differs by a factor of 16:

ProductDefault password hashCPU per loginLogins per second on 2 vCPU
KeycloakArgon2id, 7 MiB memory, 5 iterations72 ms26.2
authentikPBKDF2-SHA256, 1,000,000 iterations1,187 ms1.8
Zitadelbcrypt, cost 14997 ms2.1
Authelia + LLDAPOPAQUE with Argon2id in LLDAP195 ms5.3

We read the Keycloak and authentik parameters from the stored credentials of the test user:

{"hashIterations":5,"algorithm":"argon2","additionalParameters":{"hashLength":["32"],"memory":["7168"],"type":["id"],"version":["1.3"],"parallelism":["1"]}}
pbkdf2_sha256$1000000

Two cores divided by the CPU per login predicts the throughput, and Keycloak, authentik and Zitadel landed within 6% of that estimate. Authelia reached about half of it, so something other than the CPU of its containers limits it under parallel load. In plain terms, at the measured rates 100 people signing in during the same minute keep authentik busy for about 57 seconds of that minute on this VM, while Keycloak clears them in 4. Under that load an authentik login waited a median of 3.3 seconds and a Zitadel login 4.7 seconds, against 0.33 seconds on Keycloak.

None of this matters for a homelab with ten users who stay signed in for weeks. It matters for a school, a call centre or anything with a morning rush, where it is the number to size CPUs against. Passkey logins skip the password hash, so moving users to passkeys cuts this cost as well. On Authelia that holds only where the access policy is one_factor, because it counts a passkey as one factor and asks for the password on two-factor sites.

Where each one runs out of memory

The RAM floor is the smallest VM that still booted the stack unattended and served logins. Visible memory is what the guest reported after the kernel took its share.

ProductLast pass (visible to the guest)First failureWhat failed
Keycloak1024 MiB (955 MiB)768 MiBThe JVM refused to start, 20 restarts: There is insufficient memory for the Java Runtime Environment to continue
authentik1280 MiB (1206 MiB)1024 MiBBooted and served 5 logins, then the kernel killed a web worker: Out of memory: Killed process 2314 (gunicorn: worke)
Zitadel768 MiB (703 MiB)512 MiBThe kernel killed the login UI: Out of memory: Killed process 1410 (next-server (v1)
Authelia + LLDAP768 MiB (703 MiB)512 MiB3 of 4 boots failed, logins returned 502 and the kernel killed Authelia: Out of memory: Killed process 2537 (authelia)

Keycloak fails for a reason you can see in the JVM error. Our guide sets mem_limit: 1536m and -XX:InitialRAMPercentage=50, so the JVM commits 768 MiB of heap at startup, more than the 703 MiB the VM had:

OpenJDK 64-Bit Server VM warning: INFO: os::commit_memory(0x00000000bcc00000, 805306368, 0) failed; error='Not enough space' (errno=12)
# There is insufficient memory for the Java Runtime Environment to continue.

Lowering the container limit to 512m didn’t rescue it. The JVM started, grew to about 310 MiB resident, and the kernel killed java instead. Keycloak with PostgreSQL wants a 1 GB VM, and on 1 GB it still took 34 seconds from boot to the first served page.

authentik is the only one that failed after it had already worked. At 1024 MiB it came up, answered the first five logins, and lost a gunicorn worker to the OOM killer within the next minute. The allocation that triggered the kill came from Ubuntu’s own package-data downloader, a reminder that the OS shares the same RAM. At 768 MiB the server and worker restarted 15 and 11 times without ever answering. Give it 1.5 GB or more in production.

Zitadel and Authelia both run in 768 MiB with room to spare. At 512 MiB Zitadel lost its Next.js login container to the OOM killer on the first boot. Authelia came up on one of four tries, which is not a floor you can rely on. The memory numbers include Ubuntu, Docker and Nginx, which share the same RAM on a small VPS.

Upgrade downtime

Each stack was installed fresh at an earlier release, given a user, and then upgraded with the commands from its guide while a probe hit the health or discovery URL every half second through Nginx. Images were pulled before the old containers stopped, so the numbers measure the restart and the database migrations, not your download speed.

ProductFrom, toOutageFailed probe responsesAfter the upgrade
Keycloak, one node26.7.5 to 26.8.010.7 s21 x 502Login worked, realm and user intact
authentik2026.5.7 to 2026.8.340.8 s46 x 503, 33 x 502, 1 x 500Login worked
Zitadelv4.18.0 to v4.19.46.6 s for the login UI, 2.1 s for the API13 x 502 on the login UI, 4 x 502 on the APILogin worked
Authelia + LLDAP4.39.15 and 0.6.2 to 4.39.28 and 0.6.34.1 s8 x 502Login worked

The long authentik window is its migrations. The server answers 503 while it migrates and 502 while the container is replaced, and a single server cannot do a rolling upgrade, so this is a maintenance window. It also refuses to skip a release line, which its guide covers. Keycloak 26.7 to 26.8 cannot roll either, because the update compatibility check flags the new login failure store and stops at exit code 3. With two nodes, as in the Keycloak guide, the same upgrade measured 19 seconds on the lab host of that guide. Patch releases that pass the check roll one node at a time with no outage at all.

Zitadel replaces two containers. On a second run we probed both: the API was gone for 2.1 seconds (1.5 seconds on the first run), while the Next.js login UI took 6.6 seconds to come back, and users cannot sign in without it. Authelia was back in 4.1 seconds, close to the 4 seconds measured in its own guide. A release with a heavy migration takes longer on any of the four, so read the release notes before you pull.

Feature comparison

On a fresh install, the four login pages look like this. Only Zitadel offers self-registration on a fresh install. The passkey button on the Authelia page comes from the passkey login our guide switches on:

Keycloak vs authentik vs Zitadel vs Authelia default login pages side by side on a fresh install

Every row was checked against the documentation or source of each project at the tested release on 6 October 2026.

FeatureKeycloakauthentikZitadelAuthelia + LLDAP
LicenseApache-2.0MIT, enterprise directory under a commercial licenseAGPL-3.0 since v3Apache-2.0 (LLDAP GPL-3.0)
OpenID Connect providerYesYesYesYes, still labelled beta
SAML 2.0 providerYesYesYesNo, on the roadmap as planning
LDAP interface for old appsNoYes, LDAP outpostNoYes, LLDAP is the LDAP server
RADIUSNoYes, EAP-TLS needs EnterpriseNoNo
Forward auth for apps without SSONo, add oauth2-proxyYes, proxy providerNo, add oauth2-proxyYes, its main job
PasskeysYesYesYesYes, counts as one factor
TOTPYesYesYesYes
SCIMInbound, supported since 26.8Inbound and outboundInbound, preview, users onlyNo
Multi-tenancyRealms, plus organizationsBrands; real tenants are Enterprise and alphaInstances and organizationsNo
Admin web UIYesYesYesNo, users live in the LLDAP UI
Self-registrationYesYesYesNo
Social and external IdP loginYesYes, the in-flow source stage is EnterpriseYesNo
LDAP or AD as a user sourceYes, with syncYes, with syncYes, checked at login, no bulk syncYes, it is the backend
Config as codeRealm import, TerraformBlueprints, TerraformTerraformOne YAML file
KubernetesOperatorHelm chartHelm chartHelm chart, beta
DatabasesPostgreSQL, MariaDB, MySQL, Oracle, SQL ServerPostgreSQL onlyPostgreSQL onlyPostgreSQL, MySQL, SQLite
RuntimeJava on QuarkusPython with Rust and Go partsGo, Next.js loginGo; LLDAP in Rust

A few rows deserve a closer look before you choose:

  • authentik dropped Redis in 2025.10. A current authentik stack is PostgreSQL only, and many older compose files and tutorials still start a Redis container it no longer uses.
  • Keycloak 26.8 promoted its inbound SCIM API from preview to supported, so an HR system can now push users into a realm without an extension.
  • Zitadel moved from Apache-2.0 to AGPL-3.0 with v3. Running it unmodified is unaffected, but offering a modified build as a network service means publishing your changes.
  • The Authelia OpenID Connect provider works and we used it for Grafana in its guide, but the project still calls it beta and warns about occasional breaking changes.

Keycloak

The heavyweight that is cheapest per login. It idles at 539 MiB, the JVM takes about 33 seconds from VM boot to the first served page, and it needs a 1 GB VM at minimum. In exchange it has the broadest protocol coverage here, the most database choices and realms that fully separate tenants. The 72 ms login cost let two cores handle about 1,500 sign-ins per minute in our test.

Keycloak protects only applications that speak OIDC or SAML. A static site or an old admin panel needs oauth2-proxy in front. The admin console puts every realm setting a click away, which is powerful and takes a while to learn. Until you replace the bootstrap admin, it also nags you with a yellow banner:

Keycloak 26.8 admin console showing the users list of the bench realm and the temporary admin banner

The Keycloak guide runs two nodes behind Nginx, which doubles the JVM memory and buys zero-downtime restarts.

authentik

The most features in one box, and the most expensive login. A forward-auth proxy, an LDAP server, RADIUS, SCIM in both directions and visual flows all ship in the same image, and that image is the largest here at 1.95 GB. It also used the most memory at idle, 715 MiB across three containers.

authentik 2026.8 admin interface overview page on the test stack

The default PBKDF2 setting of 1,000,000 iterations is what costs 1.2 seconds of CPU per login. That is the Django default, and authentik doesn’t lower it. The first start ran every migration and took 84 seconds before the server answered. An upgrade is the slowest here too, at 40.8 seconds of outage. Upgrades must go one release line at a time, which the server enforces at startup.

Zitadel

The leanest of the three full identity platforms: 214 MiB at idle, 0.96 GB of images, a first start of 8 seconds once PostgreSQL was up, an upgrade that kept the login page away for 6.6 seconds and a RAM floor of 768 MiB. It has organizations and instances for real multi-tenancy, a Session API that makes custom login pages practical, and SAML and OIDC both. The cost is bcrypt at cost 14, close to a second of CPU per password login, which puts it next to authentik in the throughput test.

Zitadel v4.19 management console users list with the test user and the first admin

Its defaults need work before real users arrive. The login page offers Register new user by default, and there is no account lockout until you set one. The Zitadel guide closes both. AGPL-3.0 is the licence to check with your legal team if you plan to modify it.

Authelia with LLDAP

The smallest stack by a wide margin: 70 MiB for four containers, 0.80 GB of images and a 768 MiB floor. Authelia is a gatekeeper rather than a full identity platform. It answers the Nginx auth_request for every request, adds TOTP and passkeys, and can act as an OIDC provider for apps like Grafana. LLDAP holds the users and gives you a basic web UI for them:

LLDAP 0.6.3 web UI users list with the admin, the Authelia bind account and the test user

There is no admin console for Authelia itself. Access rules, clients and policies live in one YAML file, and changing them means a restart. It has no SAML, no self-registration and no social login. For a homelab or a small team that wants MFA in front of a handful of web apps, those gaps rarely matter and the footprint does.

Authelia 4.39 login portal with username, password and sign in with a passkey

Which one to run for each job

  • Apps that only speak SAML, many databases to choose from, or 1,500 logins per minute on two cores: Keycloak. Its 72 ms login is the cheapest here by a factor of almost three.
  • One box that also has to be an LDAP server, a RADIUS server and a forward-auth proxy, with flows built in a UI: authentik. Budget about 1.2 cores per login per second at peak, 1.5 GB of RAM or more, and a maintenance window for upgrades.
  • Customer-facing logins, separate organizations per client, or a custom login page on an API: Zitadel, at 214 MiB idle.
  • MFA in front of self-hosted web apps on a small VPS or a Raspberry Pi class host: Authelia with LLDAP, at 70 MiB and three times the login rate of authentik on the same cores.

Keep reading

UFW Firewall Commands with Examples on Ubuntu 26.04 / 24.04 Security UFW Firewall Commands with Examples on Ubuntu 26.04 / 24.04 PHP2Shell (CVE-2026-109618, CVE-2026-109619): Check and Patch PHP Php PHP2Shell (CVE-2026-109618, CVE-2026-109619): Check and Patch PHP PHP2Shell on End-of-Life PHP 8.1, 8.0 and 7.4 (WHMCS, ionCube) Php PHP2Shell on End-of-Life PHP 8.1, 8.0 and 7.4 (WHMCS, ionCube) Upgrade Kali Linux to the Latest Version Kali Linux Upgrade Kali Linux to the Latest Version Best Cybersecurity and Penetration Testing Books for 2026 Books Best Cybersecurity and Penetration Testing Books for 2026 How To Run Emby Media Server in Docker Container Containers How To Run Emby Media Server in Docker Container

Leave a Comment

Press ESC to close