Authelia + LLDAP: Self-Hosted SSO That Doesn't Weigh a Ton

Authelia + LLDAP: Self-Hosted SSO That Doesn't Weigh a Ton

Authentik is great, but sometimes you want something lighter. Here's how to set up Authelia with LLDAP as your auth gateway — no bloated dependencies, no headache.

💡 Disclosure: This article contains affiliate links. If you make a purchase through these links, we may earn a small commission at no extra cost to you. This helps support the site and keeps the content free.

I ran Authentik for over a year. It’s great. It’s powerful. It also feels like deploying a Kubernetes cluster just to get a login page.

Last month I replaced it with Authelia + LLDAP on a $6 Hetzner VPS, and the memory went from 1.2GB idle to 180MB. The login flow is faster. The config is simpler. And I still get single sign-on across all my self-hosted apps.

Here’s the thing: most of us don’t need SAML, OIDC providers, or a full identity governance platform. We need a login page that says “who are you?” and a directory that remembers the answer. That’s exactly what this stack does.

What we’re building

Two containers, one goal:

  • Authelia is the auth middleware. It sits in front of your reverse proxy and intercepts requests to protected subdomains. No credentials, no access. It handles 2FA, session management, and access rules.
  • LLDAP is the user directory. Think of it as a lightweight LDAP server that stores usernames, passwords, email, and group membership. It’s designed specifically for homelabs and self-hosters — no Active Directory-level complexity.

Together they replace the need for per-app authentication. You log in once, and every app behind Authelia trusts that session.

Here’s the flow:

Browser → Your App (e.g., grafana.example.com)

         Reverse Proxy (Caddy / NPM / Traefik)

         Authelia Auth Request → LLDAP (user check)

         If valid → session cookie → access granted

Why not Authentik?

I get asked this a lot. Let me be clear: Authentik is brilliant. If you need OAuth2 providers, SAML integration, or a fancy admin dashboard, it’s the right tool.

But most self-hosters run maybe 5-10 apps. Grafana, Portainer, Homer, a dashboard, a file sync. All of them support either forward auth or basic auth. None of them need SAML.

Authelia is simpler to configure, lighter on resources, and its config file is a single YAML that you can read in one sitting. Authentik’s config is spread across a database, a web UI, and about 20 environment variables. There’s a time and place for that power. Monday morning, setting up a quick auth gateway, is not that time.

Let me show you what I mean.

The Compose File

Here’s the docker-compose.yml I run. It’s three services: Authelia, LLDAP, and a Redis (Authelia needs it for session storage):

# docker-compose.yml
version: '3.8'

services:
  authelia:
    image: authelia/authelia:latest
    container_name: authelia
    volumes:
      - ./authelia/config:/config
    ports:
      - "127.0.0.1:9091:9091"
    environment:
      - TZ=UTC
    depends_on:
      - lldap
      - redis
    restart: unless-stopped
    networks:
      - auth-network

  lldap:
    image: lldap/lldap:latest
    container_name: lldap
    volumes:
      - ./lldap/data:/data
    ports:
      - "127.0.0.1:3890:3890"
      - "127.0.0.1:17170:17170"
    environment:
      - LLDAP_JWT_SECRET=change-me-to-a-random-string
      - LLDAP_LDAP_USER_PASS=change-me-to-another-random-string
      - LLDAP_LDAP_BASE_DN=dc=home,dc=local
    restart: unless-stopped
    networks:
      - auth-network

  redis:
    image: redis:7-alpine
    container_name: authelia-redis
    restart: unless-stopped
    networks:
      - auth-network

networks:
  auth-network:
    driver: bridge

One thing I learned the hard way: use random strings for secrets. I once reused a password across three test environments, then forgot which one was still in production. Generate them with openssl rand -base64 32 and write them somewhere safe.

Configuring LLDAP

LLDAP is refreshingly simple. Once it’s running, open http://your-server:17170 in a browser. You’ll see a setup page:

  1. Create the admin account (this is your directory manager, not your everyday user)
  2. Add users: I create one per person in my household
  3. Add groups: I use admin and users — that’s it

The UI is basic but functional. You add a user, set a password, and optionally assign them to a group. That’s the entire user management workflow. No schemas, no organizational units, no LDIF files.

LLDAP exposes an LDAP interface on port 3890. Authelia will connect to it.

Configuring Authelia

This is where most people get stuck, so I’ll spell it out. Create authelia/config/configuration.yml:

########################################################
# Authelia configuration
########################################################

host: 0.0.0.0
port: 9091

log:
  level: info

theme: dark

jwt_secret: another-random-string-here

default_redirection_url: https://auth.example.com

totp:
  issuer: authelia.example.com
  period: 30
  skew: 1

authentication_backend:
  ldap:
    address: ldap://lldap:3890
    implementation: lldap
    user: uid=admin,ou=people,dc=home,dc=local
    password: the-ldap-user-pass-you-set
    base_dn: dc=home,dc=local
    users_filter: (&(|({username_attribute}={input})({mail_attribute}={input}))(objectclass=person))
    groups_filter: (&(member={dn})(objectclass=groupOfUniqueNames))
    attributes_map:
      display_name: displayName
      mail: mail

access_control:
  default_policy: deny
  rules:
    - domain: "auth.example.com"
      policy: bypass
    - domain: "grafana.example.com"
      policy: two_factor
    - domain: "portainer.example.com"
      policy: one_factor
    - domain: "*.example.com"
      policy: one_factor
      resources:
        - "^/api(/.*)?$"

session:
  name: authelia_session
  secret: session-secret-string
  expiration: 1h
  inactivity: 30m
  domain: example.com
  redis:
    host: redis
    port: 6379

regulation:
  max_retries: 5
  find_time: 2m
  ban_time: 5m

storage:
  local:
    path: /config/db.sqlite3

notifier:
  filesystem:
    filename: /config/notifications.yml

Let me explain the important parts:

  • default_policy: deny — Everything is blocked by default. You explicitly allow what you want.
  • two_factor vs one_factortwo_factor requires TOTP (Google Authenticator, Authy). I use it for Grafana because losing my dashboards would hurt. one_factor is just password + session. Fine for most apps.
  • The bypass rule for auth.example.com — Authelia’s own domain needs to be accessible without auth, otherwise you get a login loop. Ask me how I know.
  • Storage — I use SQLite because it’s one file. No extra database container. If you’re running 100+ users, switch to Postgres. For a household of 2-5 people, SQLite is fine.

Wiring it to your reverse proxy

This is where the magic happens. I use Caddy, so here’s my Caddyfile:

auth.example.com {
    reverse_proxy authelia:9091
}

grafana.example.com {
    forward_auth authelia:9091 {
        uri /api/verify
        request_method GET
        request_header X-Forwarded-Method {method}
        request_header X-Forwarded-Proto {scheme}
        request_header X-Forwarded-Host {host}
        request_header X-Forwarded-URI {uri}
        copy_headers Remote-User Remote-Groups Remote-Email Remote-Name
    }
    reverse_proxy grafana:3000
}

If you use Nginx Proxy Manager, you add a custom location block with auth_request. If you use Traefik, you use the ForwardAuth middleware. The principle is the same: the proxy asks Authelia “is this request authenticated?” before forwarding it to the app.

What this feels like day-to-day

You open grafana.example.com in a browser. Authelia redirects you to auth.example.com. You type your email and password. If the policy requires 2FA, you enter a TOTP code. A session cookie is set, and you’re dropped into Grafana.

Open portainer.example.com in a new tab. Straight in. No login screen. The session is shared.

This is the “single” in single sign-on. It sounds trivial, but the first time you experience it across five different apps, you realize how much friction you were carrying around.

What I wish I knew

LLDAP’s admin UI is not well secured. It’s on port 17170 with only a basic password prompt. I put it behind a firewall rule and only access it through WireGuard. Don’t expose it to the internet.

Authelia’s notifier is janky. By default it tries to send email for password resets. I use the filesystem notifier (shown above) which writes notifications to a local file. It’s not ideal for password resets, but for a homelab, you can just reset passwords directly in LLDAP’s admin UI.

Session timeouts matter. I set inactivity to 30 minutes and expiration to 1 hour. If you set them too long and someone walks away from their desk, any of your apps is accessible. If you set them too short, you’re logging in five times a day. Find your balance.

You can bypass auth for specific endpoints. The resources rule in the config above lets API calls through without auth. This is useful for Grafana’s alerting webhooks or Portainer’s agent endpoints. Don’t copy-paste my config — check what your apps need.

Is this for you?

This stack is for the middle ground between “no auth at all” and “full enterprise identity platform.”

You should use it if:

  • You run 3-15 self-hosted apps behind a reverse proxy
  • You want one login for everything, not per-app credentials
  • You have a VPS or homelab and want to keep resource usage low

You should use Authentik if:

  • You need OAuth2 providers (for things like GitLab, Mastodon, or Nextcloud SSO)
  • You want a web UI for managing everything
  • You have more than 20 users or complex group policies

For most people reading this? Authelia + LLDAP is the sweet spot. It’s the auth layer I wish I’d started with, instead of the one I upgraded to.

🚀NordVPN

Running a VPS with auth middleware? Secure every connection with a reliable VPN.

Get NordVPN →

Affiliate link — we may earn a commission at no extra cost to you.

Stay in the loop 📬

Get self-hosting tutorials, tool reviews, and infrastructure tips delivered to your inbox. No spam, unsubscribe anytime.

Join 0 self-hosters. Free forever.