
One Login for Everything: lldap and Authelia
By late January the platform had exactly one real application, the mail server, and I was already deploying single sign-on. That ordering looks premature and turned out to be one of the better calls of the project: every service added since, and there have been many, arrived into a world where identity was a solved problem. One account per human, managed in one place, wired into everything.
The original plan was more cautious. Phase one would use Stalwart’s internal user directory, phase two would migrate to a proper SSO stack later. Halfway through the research I dropped the phasing: migrating my family’s accounts twice sounded worse than building the real thing immediately. Zero throwaway work, and the architecture running today is the one from that decision.
Small enough to not think about
The identity stack is two pieces. lldap is a minimal LDAP server with a clean web UI: users, groups, nothing else. Authelia sits in front as the authentication portal, speaking OIDC to applications that support it and acting as a forward-auth gate for applications that don’t. Sessions live in the same managed Valkey the rest of the platform uses.
The alternative was Authentik, which does more and costs more: 2 to 4 GB of RAM against roughly 60 MB for Authelia plus lldap. That’s a 40x difference, and it’s the difference between needing a bigger instance and running the whole identity layer on a standard.small with room to spare. Both chosen tools are memory-safe (Go and Rust), both are boring in the good way. Identity got its own dedicated node, same isolation logic as giving mail its own instances: the thing everything depends on shouldn’t share a blast radius with experiments.
The two integration modes matter in practice. Applications with real OIDC support do a proper login flow with Authelia and get identity claims. Applications with no auth story of their own get protected at the proxy instead: Traefik’s forward-auth middleware asks Authelia “is this session valid?” before any request reaches the backend. Between the two modes, nearly every service added since has plugged into the same two boxes instead of growing its own login system.
The decision I made twice
Last week I mentioned getting to make an OIDC decision twice. Here it is. The plan had Stalwart authenticating users through Authelia via OIDC, one identity provider ruling everything, no LDAP binds in sight. I built it that way. Then reality arrived: mail clients don’t cooperate. IMAP and SMTP clients on phones and desktops authenticate with a username and password; support for OAUTHBEARER against an arbitrary third-party OIDC provider is essentially nonexistent outside the big providers’ own apps.
So, mid-story, the course correction: Stalwart authenticates users directly against lldap with an LDAP bind. Same accounts, same passwords, same directory, different protocol. Web applications kept OIDC; mail kept LDAP. The lesson that stuck: the elegant single-protocol architecture existed only in diagrams, and the users’ actual devices get a vote. They voted for the 30-year-old protocol.
The loop that feeds itself
My favorite property of the finished stack is its circularity. Stalwart delivers Authelia’s two-factor enrollment emails through its SMTP notifier. Authelia’s forward-auth protects lldap’s admin UI. lldap authenticates Stalwart’s users. Three services, each guarding another, no external identity dependency anywhere: no Google login, no SaaS IdP, no cloud MFA service. The failure modes of a circle need respect (a later post covers the cold-boot deadlock this eventually produced on the SSO node), but the sovereignty is total.
Not everything here is tidy. The webmail itself sits outside the 2FA perimeter, because Roundcube authenticates over IMAP and the second factor lives in Authelia; that’s a documented, accepted risk, revisited but not yet resolved. And the story that delivered all this was badly overloaded: a Terraform layer, two new Ansible roles, Traefik wiring, DNS, secrets, plus the OIDC reversal, all under a story named “Admin Account Setup”. The retrospective was blunt: when scope accelerates, story boundaries have to be redrawn with it. Process debt is quieter than technical debt, and just as real.
Next week: what all this identity machinery was actually for. Family accounts, including email for a nine-year-old.
You May Also Like

SPF, DKIM, DMARC: Getting Mail Delivered
Running a mail server is easy. Convincing Gmail that your mail server exists legitimately is the actual work.

LUKS on Every Data Volume
Full-disk encryption for servers I will never physically see, and why the unlock step stays manual.

Secrets That Live in Git
How SOPS and two GPG keys let me commit every credential this platform needs to the repository itself.



