You’ve got Gitea running. Then Grafana. Then Nextcloud. Then Wiki.js. Then Mealie. Then Immich. Then Uptime Kuma. Then Portainer. Then Jellyfin. Then…
Stop. You’ve got 15 services now, 15 different passwords, and it’s Saturday morning. You wanted to glance at a dashboard before coffee, and instead you’re fumbling with a username field for the fourth time. You forgot which password was which. You’re using the same one everywhere (don’t do that). It’s 2 AM and you’re resetting credentials over SSH instead of having a login page that actually works.
This is what Single Sign-On (SSO) solves. Log in once, access everything. It’s how enterprises have worked for decades. It’s how your Google account works across every Google product. And with one Saturday afternoon, it’s how your home lab can work too — no corporate IT department, no CISM certification, no second mortgage required.
In the self-hosted world, there are two ways to do it: Authelia and Authentik. One is a bouncer at the door. The other is the entire security checkpoint. Picking right depends on what you actually need versus what sounds cool at 2 AM when you’re reading docs instead of sleeping.
Full example: Working Compose files for both setups live at github.com/KingPin/sumguy-examples/tree/main/security/authentik-vs-authelia.
Why Bother With SSO in a Home Lab?
Fair question. You’re the only user. Who are you protecting it from — yourself?
Kind of, yeah. But also:
- Convenience: One login for everything is genuinely life-improving.
- Security: Strong 2FA in one place rather than inconsistently across services.
- Access control: Decide which services require auth before they even load.
- Auditability: One place to see who logged in, from where, when.
- Bragging rights: “Yeah, I run my own identity provider” is excellent dinner-party material. Probably.
More practically: a lot of self-hosted apps have mediocre built-in authentication. Some have none at all. Putting an auth layer in front of them means you’re not trusting a random open-source project’s login form to protect your network.
The Problem: 15 Doors, 15 Keys
When you self-host a lot of stuff, each service either:
- Has its own user management (and you create duplicates everywhere)
- Doesn’t have user management at all (open to the world, hope nobody bad finds it)
- Trusts a header from your reverse proxy (requires the proxy to vouch for you)
- Speaks OIDC/SAML natively (modern apps — Gitea, Grafana, Nextcloud)
Options 3 and 4 are where SSO lives. Your reverse proxy (or identity provider) sits in front of everything. Before letting a request through, it says “Who are you?” You prove it once with a password (or 2FA), then your services trust the answer and let you in.
The difference between Authelia and Authentik is how much machinery you want behind that vouching system.
Authelia: The Lightweight Bouncer
Authelia is forward auth. It’s a small Go service that sits next to your reverse proxy and handles login. That’s it. That’s the whole job.
Your flow looks like this:
- You hit
grafana.home.internal - Traefik sees the request, asks Authelia: “Is this person logged in?”
- Authelia checks a session cookie. No cookie? Redirect to
auth.home.internal/login - You type username/password. Authelia verifies against local user database (or LDAP)
- Authelia prompts for 2FA if your policy requires it
- Authelia sets a session cookie, redirects you back to Grafana
- Traefik checks again: “Yep, they’re good”
- Traefik forwards the request to Grafana with headers like
Remote-User: kingpin - Grafana sees that header and logs you in as
kingpin
Authelia doesn’t manage users through a fancy UI. You write usernames, password hashes, and group memberships in a YAML file. Or you point it at LDAP and let Active Directory (or FreeIPA) manage people.
What Authelia does well:
- TOTP 2FA (Aegis, Authy, Bitwarden, anything that does RFC 6238)
- WebAuthn / FIDO2 hardware keys, Touch ID, Windows Hello
- Fine-grained access rules — this subdomain needs 2FA, that one just needs a password
- Drop-in
forwardAuthmiddleware for Traefik and Nginx - Tiny — typically 50-100 MB RAM in use, single Go binary, SQLite by default
Authelia + Traefik: The Compose Setup
services: authelia: image: authelia/authelia:latest container_name: authelia restart: unless-stopped ports: - "9091:9091" environment: AUTHELIA_JWT_SECRET: ${AUTHELIA_JWT_SECRET} AUTHELIA_SESSION_SECRET: ${AUTHELIA_SESSION_SECRET} AUTHELIA_STORAGE_ENCRYPTION_KEY: ${AUTHELIA_STORAGE_ENCRYPTION_KEY} volumes: - ./authelia-config.yml:/config/configuration.yml:ro - ./users.yml:/config/users.yml:ro - authelia-data:/var/lib/authelia networks: - traefik
traefik: image: traefik:latest ports: - "80:80" - "443:443" volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - ./traefik-config.yml:/traefik.yml:ro networks: - traefik
grafana: image: grafana/grafana:latest environment: GF_AUTH_PROXY_ENABLED: "true" GF_AUTH_PROXY_HEADER_NAME: "Remote-User" GF_AUTH_PROXY_HEADER_PROPERTY: "username" GF_AUTH_PROXY_AUTO_SIGN_UP: "true" labels: - "traefik.http.routers.grafana.rule=Host(`grafana.home.internal`)" - "traefik.http.routers.grafana.middlewares=authelia@docker" - "traefik.http.middlewares.authelia.forwardauth.address=http://authelia:9091/api/verify?rd=https://auth.home.internal" - "traefik.http.middlewares.authelia.forwardauth.trustForwardHeader=true" - "traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=Remote-User,Remote-Groups,Remote-Name,Remote-Email" networks: - traefik
volumes: authelia-data:
networks: traefik: driver: bridgeThe magic is that forwardauth middleware. Traefik checks with Authelia on every request. No login? Redirect. Logged in? Pass through with Remote-User, Remote-Groups, Remote-Name, and Remote-Email headers attached.
Authelia Config (YAML)
jwt_secret: ${AUTHELIA_JWT_SECRET}
session: secret: ${AUTHELIA_SESSION_SECRET} cookies: - name: authelia_session domain: home.internal authelia_url: https://auth.home.internal inactivity: 1h expiration: 24h
storage: encryption_key: ${AUTHELIA_STORAGE_ENCRYPTION_KEY} local: path: /var/lib/authelia/db.sqlite3
authentication_backend: file: path: /config/users.yml
access_control: default_policy: deny rules: - domain: "*.home.internal" policy: one_factor subject: - "group:users" - domain: "vault.home.internal" policy: two_factor subject: - "group:users"
totp: period: 30 skew: 1
webauthn: display_name: SumGuy Homelab timeout: 60s usernameless: false
notifier: filesystem: filename: /config/notification.txtdefault_policy: deny means nothing is allowed unless an explicit rule says so. Safer than the inverse. Rules are evaluated top-down — the first match wins, so put your stricter two_factor rules above the looser one_factor catch-all.
For 2FA, Authelia supports TOTP, WebAuthn, and Duo. You enable it per user in the users YAML, or per domain in the policy above. Clean. Simple. No extra UI.
The Trade-Off: Authelia’s Limits
Authelia doesn’t have:
- A user-facing admin panel (you edit YAML or LDAP)
- User self-enrollment (you add them manually or via LDAP sync)
- Social login (GitHub, Google, etc.)
- Full OIDC provider mode (basic OIDC exists, but it’s not the main use case)
- SAML
- LDAP server (it’s an LDAP client only)
If you’re the only person on your homelab, or you’ve got a static team and LDAP handles it, Authelia is perfect. It weighs nothing, starts instantly, and you’re running one extra container.
If you need users to sign up themselves, or you want to let external apps use your homelab as a login provider, you need something bigger.
Authentik: The Full Identity Provider
Authentik is a different beast. Where Authelia is a bouncer, Authentik is the HR department, security desk, and visitor management system rolled into one.
It’s a full Identity Provider (IdP) — other applications delegate their entire authentication to Authentik using industry protocols like OIDC, SAML, and LDAP. Instead of proxying requests via forward auth, apps talk to Authentik directly and trust its answer.
The typical flow with Authentik looks like:
- App (Gitea) is configured with Authentik as an OIDC provider
- User hits Gitea, clicks “Sign in with Authentik”
- Gets redirected to Authentik’s login page
- Authentik handles all the auth (password, 2FA, enrollment, whatever your policy says)
- Redirects back to Gitea with an authorization code
- Gitea exchanges the code for a token, logs the user in
What Authentik does well:
- Full OIDC / OAuth2 provider — apps like Grafana, Gitea, Nextcloud, Portainer log in natively
- SAML for enterprise-y stuff
- LDAP server — apps that only speak LDAP can use it as a backend
- Beautiful web UI for managing users, groups, applications, and policies
- Customizable login flows (branding, MFA enrollment, password reset)
- Social login as an upstream source (GitHub, Google, Discord, etc.)
- Outposts — small proxies Authentik manages for forward auth, like Authelia’s approach
What it costs you:
- More RAM — expect 500 MB – 1 GB at rest with PostgreSQL and Redis
- More complexity — “flows”, “providers”, “outposts” have a learning curve
- More moving parts — needs PostgreSQL and Redis to function
Authentik Docker Compose
services: postgresql: image: postgres:16-alpine container_name: authentik-db restart: unless-stopped environment: POSTGRES_PASSWORD: ${PG_PASS} POSTGRES_USER: authentik POSTGRES_DB: authentik volumes: - ./database:/var/lib/postgresql/data networks: - authentik
redis: image: redis:alpine container_name: authentik-redis restart: unless-stopped command: --save 60 1 --loglevel warning volumes: - ./redis:/data networks: - authentik
server: image: ghcr.io/goauthentik/server:2024.2 container_name: authentik-server 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: "false" ports: - "9000:9000" - "9443:9443" depends_on: - postgresql - redis networks: - authentik - proxy
worker: image: ghcr.io/goauthentik/server:2024.2 container_name: authentik-worker 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} depends_on: - postgresql - redis networks: - authentik
networks: authentik: internal: true proxy: external: trueAfter it’s up, hit https://your-domain.com/if/flow/initial-setup/ to create your admin account. The UI is genuinely pleasant once you find your bearings.
Authentik’s User Management
Unlike Authelia, Authentik has a web UI at https://authentik.home.internal/if/admin/. You can:
- Create users manually or import via CSV/LDAP
- Set up enrollment flows (new users sign up, optionally with approval)
- Create groups and assign permissions
- Configure 2FA policies per group
- Set password complexity rules
- Enable social login as an upstream source
- See audit logs of who logged in where, when, from what IP
Wiring Up Gitea via OIDC
In Authentik, you create a Provider (OIDC, with Gitea’s callback URL) and an Application (Gitea, pointing at that provider). Then in Gitea’s admin panel:
- OAuth2 Provider: OpenID Connect
- Client ID and Secret (from Authentik)
- Discovery URL:
https://auth.home.internal/application/o/gitea/.well-known/openid-configuration - Scopes:
openid profile email
Authentik auto-creates Gitea users on first login. Done. Same pattern works for Grafana, Nextcloud, Portainer, Argo CD, Vaultwarden’s admin panel, basically anything modern.
Authentik for Forward Auth (Outposts)
For services that don’t speak OIDC (looking at you, half the home-lab dashboard ecosystem), Authentik can still protect them. You deploy a small outpost container, configure it in the Authentik UI, and point your Traefik forwardAuth middleware at the outpost instead of Authelia. Same end result; more moving parts; centralized in the web UI.
Head-to-Head Comparison
| Feature | Authelia | Authentik |
|---|---|---|
| Resource use | ~50-100 MB RAM | ~500 MB – 1 GB RAM |
| Setup complexity | Low-Medium | Medium-High |
| Web admin UI | No (YAML / LDAP) | Yes (full admin UI) |
| Forward Auth | Yes (primary use case) | Yes (via outpost) |
| OIDC provider | Limited (basic only) | Full |
| SAML provider | No | Yes |
| LDAP server | No (client only) | Yes |
| 2FA options | TOTP, WebAuthn, Duo | TOTP, WebAuthn, SMS, Duo, more |
| Database | SQLite (default) | PostgreSQL + Redis (required) |
| User self-signup | No | Yes (configurable flows) |
| Social login | No | Yes (GitHub, Google, Discord, etc.) |
| User management | YAML files or LDAP | Web UI + LDAP |
| Written in | Go | Python |
| Plays well with Traefik | Excellent (drop-in) | Good (via outpost) |
| Learning curve | Low | Medium |
Common Gotchas (Save Yourself the 2 AM Debug)
Neither tool is hard to set up. Both have one or two ways to mess up that will eat your evening if you don’t know to look for them.
Cookie domain mismatch (Authelia). The session.cookies.domain in your Authelia config has to be the parent of every protected service. If your services live at grafana.home.internal and git.home.internal, the cookie domain must be home.internal — not auth.home.internal. Wrong domain means the cookie won’t be sent on requests to other subdomains, and Authelia will think you’re logged out every time you cross a service boundary. Classic source of “wait, why does it keep asking me to log in?”
HTTPS is non-negotiable. Secure cookies require HTTPS. Forward auth without TLS works just well enough to fool you into thinking everything’s fine in dev, then collapses in confusing ways in prod. Run Let’s Encrypt (via Traefik or Caddy) from day one. See Caddy vs Traefik if you’re picking a proxy.
Authentik outpost not connecting. When you create a forward-auth outpost in the UI, Authentik gives you a Compose snippet or a Kubernetes manifest. Use the one it gives you — the embedded outpost token has to match. If you copy a generic outpost config and paste your own token, the outpost will start but never appear as “healthy” in the Authentik UI.
Order of access_control rules. Authelia matches rules top-down. Put two_factor rules above broader one_factor ones, or your stricter policies will never fire. Use default_policy: deny so you fail closed, not open.
Forgetting to back up Authentik’s database. Authelia’s state lives in a SQLite file you can tar and forget. Authentik’s lives in PostgreSQL, and losing it means every WebAuthn key, every TOTP secret, every user config — gone. Use restic or kopia to back up the postgres volume on a schedule.
Time skew on TOTP. If your homelab clock drifts, TOTP codes start failing intermittently. Run chrony or systemd-timesyncd. The error message just says “invalid code” — it does not tell you your clock is 47 seconds off.
Mixing OIDC and forward auth on the same app. If Gitea already does OIDC against Authentik, don’t also put it behind a forward-auth middleware. You’ll bounce between two login flows, neither of which knows about the other. Pick one path per app.
Which Apps Speak Native OIDC?
A non-exhaustive list of home-lab favorites and whether you’ll need forward auth or can use real OIDC. If “Native OIDC” is Yes, prefer Authentik (or any IdP — Keycloak, Zitadel, etc.). If No, both Authelia and Authentik (via outpost) cover it.
| App | Native OIDC | Notes |
|---|---|---|
| Gitea / Forgejo | Yes | OAuth2 + OIDC built-in |
| Grafana | Yes | Generic OAuth + header proxy modes |
| Nextcloud | Yes (via app) | user_oidc app |
| Portainer | Yes | Business edition; Community has OAuth |
| Vaultwarden | Partial | Admin panel only; user vault doesn’t do OIDC |
| Jellyfin | Plugin | jellyfin-plugin-sso adds it |
| Immich | Yes | First-class OAuth2 |
| Home Assistant | Yes (via add-on) | auth_oidc custom component |
| Paperless-ngx | Yes | Via mozilla-django-oidc |
| Outline | Yes | First-class OIDC |
| AdGuard Home | No | Header auth only |
| Uptime Kuma | No | Header auth only |
| Pi-hole | No | Header auth only |
If your stack is mostly “Yes” rows, Authentik earns its RAM. If it’s mostly “No” rows, save the gigabyte and run Authelia.
Migration Path: Authelia → Authentik
Plenty of people start with Authelia, outgrow it, and want to switch. You don’t have to flip everything at once. Run them in parallel during the cutover:
- Stand up Authentik on a different subdomain (
authentik.home.internal) alongside the existing Authelia (auth.home.internal). They don’t conflict. - Import users. Authentik has a CSV import, or you can script it via its API. WebAuthn keys and TOTP secrets do not migrate — users re-enroll on first login. Warn them.
- Convert OIDC-capable apps first. Gitea, Grafana, Nextcloud — point them at Authentik directly. Test, confirm logins work, leave Authelia protecting the rest.
- Move forward-auth services to Authentik outposts one at a time. Change one Traefik middleware label, restart, test.
- Tear down Authelia when nothing references it anymore.
The hidden cost is the re-enrollment friction for 2FA. If you have non-technical users on the lab, plan a one-evening “we’re all re-enrolling” session, ideally with a backup plain-password login path you disable afterward.
Honorable Mention: Tinyauth
If Authelia feels too heavy and you literally just want “any login at all in front of these apps,” check out Tinyauth. It’s a single-binary forward-auth daemon with basic 2FA and OAuth login (GitHub, Google) and exactly zero of Authentik’s identity-provider machinery. ~10 MB RAM, one container, config in a single file. Not the right tool if you have more than one user or want fine-grained policies, but worth knowing exists.
Which One Should You Use?
The honest answer: start with Authelia, graduate to Authentik when you outgrow it.
If you’re new to self-hosted SSO, Authelia will get you protected services with 2FA in an afternoon. The YAML config is approachable, the Traefik integration is clean, and you’re not running three extra containers just to get a login page. When you start wanting native OIDC on Gitea and Grafana, or a web UI to manage users, or you start onboarding other people — that’s when Authentik earns its extra RAM.
Pick Authelia if:
- You’re the only person using the homelab (or a small fixed team)
- You’re running on constrained hardware (Pi, tiny VPS, single mini-PC)
- You’re comfortable with YAML config files
- You mostly just want a login wall with 2FA in front of services
- Your team is already in LDAP / Active Directory / FreeIPA
Pick Authentik if:
- You want apps like Grafana, Gitea, and Nextcloud doing native OIDC SSO
- You want a web UI to manage users and see who logged in where
- You have other people using your home lab
- You need LDAP for legacy apps
- You want social login or self-service enrollment
- You have the RAM to spare
Run both if you’re the kind of person who deploys things just to learn them — which, given that you’ve read this far into a homelab authentication guide, is almost certainly you. Authentik as your IdP for OIDC-capable apps, Authelia (or an Authentik outpost) for the stragglers.
Related Reading
- Vaultwarden Behind Authelia: 2FA That Holds — a concrete worked example of forward auth in front of a service that needs it
- Caddy vs Traefik — picking the reverse proxy that sits in front of your IdP
- WebAuthn / Passkeys for Sysadmins — the 2FA story without the TOTP friction
- Headscale: Self-Hosted Tailscale — pairs nicely with an internal-only IdP
The Bottom Line
Stop logging into twelve services with twelve passwords like some kind of digital cave person. You’ve already gone to the trouble of self-hosting everything — spend one Saturday afternoon putting a real auth layer in front of it.
Authelia: light, fast, file-based, perfect for Traefik forward auth. Authentik: full IdP, web UI, OIDC/SAML, more powerful and more complex. Both are worth knowing. Both will make your home lab meaningfully better.
Your 2 AM self will thank you. Now go drink that coffee before it gets cold.