Set Up LLDAP and Authelia for Simple Self-Hosted SSO
Use LLDAP as the user directory, let Authelia provide the login wall, and keep the scope to one sane LDAP-backed path instead of chasing a giant identity platform before you need it.
How to stand up LLDAP and Authelia, create a service bind user, connect the LDAP backend, and protect one internal app with a simple gate.
Small self-hosted stacks that need one directory for users and groups plus a browser login wall for dashboards and admin tools.
The dangerous mistake is exposing the real app publicly while assuming the proxy login wall alone fixes everything.
Before you begin
- A Linux host with Docker Compose v2.
- An existing reverse proxy entry point such as Nginx, Caddy, or Traefik.
- One internal web app you want to protect, already bound privately or reachable only on an internal Docker network.
- A domain you control, such as
auth.example.comanddash.example.com.
Current Authelia guidance includes a specific integration example for LLDAP and assumes the LDAP server is already reachable by Authelia. LLDAP's own upstream docs position it as a lightweight authentication directory rather than a full enterprise LDAP suite, which is exactly why it fits a small self-hosted stack well.
Step 1: Keep the architecture simple
Do not try to solve every identity problem on day one. Start with one narrow flow:
- LLDAP holds users and groups.
- Authelia uses LLDAP as the authentication backend.
- Your reverse proxy sends protected requests through Authelia before the app sees them.
- The upstream app stays private on localhost or on an internal-only Docker network.
This keeps the trust boundary obvious. LLDAP manages identity data. Authelia handles the login session. The proxy decides whether traffic may reach the app. If you skip that separation, debugging gets messy fast.
Step 2: Run LLDAP and create the bind user
Create a stack directory and prepare config storage:
mkdir -p /opt/stacks/auth/lldap/data
mkdir -p /opt/stacks/auth/authelia/config
cd /opt/stacks/auth
Start from a Compose file like this:
services:
lldap:
image: lldap/lldap:stable
container_name: lldap
restart: unless-stopped
ports:
- 127.0.0.1:17170:17170
- 127.0.0.1:3890:3890
volumes:
- ./lldap/data:/data
authelia:
image: authelia/authelia:latest
container_name: authelia
restart: unless-stopped
volumes:
- ./authelia/config:/config
depends_on:
- lldap
This keeps both services private by default. LLDAP exposes its web UI and LDAP listener only on localhost for first setup. If you later split services across multiple hosts, revisit the bind and network plan deliberately instead of broadening it casually.
Start LLDAP first and open the web UI through SSH port forwarding or your existing private proxy path:
docker compose up -d lldap
docker compose ps
docker compose logs --tail 50 lldap
Inside the LLDAP UI:
- Create your normal user account and at least one group that will represent access to a protected app.
- Create a dedicated service account for Authelia, such as
authelia. - Give that service account only the permissions needed to read user and group attributes and, if you want password resets, to change passwords.
Authelia's LLDAP example uses the LLDAP-specific backend implementation and binds with a user DN such as
UID=authelia,OU=people,DC=example,DC=com. Save the bind password in a secret file or protected env source rather than hardcoding it
into a repo.
Step 3: Configure Authelia to use LLDAP
Create /opt/stacks/auth/authelia/config/configuration.yml with the minimum pieces required for a first pass:
authentication_backend:
ldap:
implementation: lldap
address: ldap://lldap:3890
base_dn: DC=example,DC=com
user: UID=authelia,OU=people,DC=example,DC=com
password: use-a-real-secret-here
access_control:
default_policy: deny
rules:
- domain: dash.example.com
policy: one_factor
session:
secret: replace-with-a-long-random-secret
cookies:
- domain: example.com
authelia_url: https://auth.example.com
storage:
local:
path: /config/db.sqlite3
notifier:
filesystem:
filename: /config/notification.txt
This follows the shape of the current Authelia LLDAP integration docs while keeping the first setup small. The default deny policy is deliberate: it prevents Authelia from silently allowing new domains you forgot to lock down.
Add your users to the right LLDAP groups before you start inventing complicated policy logic. For a first protected service, one-factor auth with a narrow domain rule is enough to prove the pipeline.
Bring up Authelia and inspect the logs for LDAP bind errors:
docker compose up -d authelia
docker compose logs --tail 100 authelia
Step 4: Protect one app through your reverse proxy
The exact reverse-proxy syntax depends on your stack, but the important pattern is stable: the public hostname talks to the proxy, the proxy asks Authelia whether the user is allowed through, and only then does the request reach the private app.
For Nginx, the high-level shape looks like this:
server {
server_name dash.example.com;
location / {
auth_request /internal/authelia/authz;
proxy_pass http://private-app;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /internal/authelia/ {
internal;
proxy_pass http://authelia:9091;
}
}
Match the exact paths and headers to the current proxy integration guidance for your chosen proxy. What matters operationally is that the app is still private, the proxy trusts only the right upstreams, and Authelia is the authentication decision point.
Step 5: Verify groups, login flow, and denial behavior
Test the stack in this order:
- Open the protected hostname while signed out. You should be redirected to Authelia.
- Log in with a real LLDAP user. You should return to the app successfully.
- Try a user who should not have access. The request should be denied rather than falling through.
- Verify the real app port is not publicly reachable outside your private path.
curl -I https://dash.example.com
curl -I http://127.0.0.1:17170
docker compose logs --tail 50 authelia
docker compose logs --tail 50 lldap
If your policies depend on groups, confirm group membership in the LLDAP UI and then re-test with a fresh browser session. Group mistakes are much more common than actual LDAP bugs in a first deployment.
Rollback notes
If the gate blocks legitimate users unexpectedly, remove the proxy auth layer first while keeping the upstream private, then repair LLDAP or Authelia with localhost-only access. Do not respond to a broken SSO rollout by exposing the app publicly.
Back up the LLDAP data directory and the Authelia config directory before bigger policy changes. The goal is to recover identity state and config together rather than trying to remember which file changed.
Troubleshooting
Authelia starts but users cannot log in.
Check the bind DN, bind password, base DN, and whether LLDAP is reachable on the address Authelia expects.
Login works but the app is still reachable directly.
Fix the upstream bind or Docker port publishing first. That is a boundary failure, not a cosmetic one.
Group-based access rules do not behave as expected.
Verify the user is actually in the intended LLDAP group and that the policy matches the right hostname.
You want MFA, many apps, and more advanced policy later.
That is fine, but prove one protected app first. Complexity is easier to add than to debug when everything is broken at once.