Blog / Tutorials / Setting Up lldap for Centralized VPS Authentication
(last updated: )

Setting Up lldap for Centralized VPS Authentication

lldap gives self-hosters a lightweight LDAP server without the usual complexity — here's how to set it up on a VPS.

10 min read

lldap is a lightweight LDAP server built for authentication. It stores users and groups, exposes them over the LDAP protocol and adds a simple web interface for management. Traditional LDAP servers like OpenLDAP demand schemas, config files and deep protocol knowledge. lldap skips that complexity and runs in a single small container. Self-hosters use it to give all their apps one shared login on a single VPS.

Why Centralized Auth Matters for Self-Hosters

Most self-hosters start with one service. A file sync tool comes first, then a password manager, a media server and a wiki. Each app ships its own user database. Every new service means another account, another password and another admin panel to manage.

This setup scales poorly. When a family member or teammate joins, you create the same account in five places. When someone leaves, you have to remember every app where they had access. Forgotten accounts stay active and become a security risk. Password changes turn into a chore, so people reuse weak passwords across services.

Centralized authentication fixes this with a single source of truth. You store users and groups in one directory. Every app checks credentials against that directory instead of its own database. Create a user once, and they can log in everywhere you allow. Disable the user once, and access ends across all connected services.

Groups add fine-grained control. You might create one group for family members and another for admins. Each app then decides who gets in based on group membership. All access rules live in one place, which makes audits simple.

There is a usability gain as well. Users remember one password instead of a dozen. You can enforce one password policy for every service. Onboarding a new person takes a minute instead of an afternoon.

LDAP is the classic protocol for this job. Almost every self-hosted app with multi-user features supports it. The catch is that traditional LDAP servers carry decades of enterprise complexity. Schemas, replication and cryptic configuration files scare off many self-hosters. lldap removes that barrier by keeping only the parts that authentication needs.

Installing lldap with Docker

Docker offers the simplest way to run lldap. The project publishes an official lldap Docker image as lldap/lldap, with builds for AMD64, ARM64 and ARM/V7. The container listens on two ports. Port 3890 serves LDAP, and port 17170 serves the web interface. The steps below assume an Ubuntu server that already runs Docker Engine and the Compose plugin.

Preparing Secrets and Admin Credentials

  1. Create a project directory and generate the secrets. lldap needs a JWT secret for web sessions and a key seed for its encryption keys. Generate both, plus a strong admin password, and store them in an .env file:
mkdir -p ~/lldap && cd ~/lldap
echo "LLDAP_JWT_SECRET=$(openssl rand -hex 32)" > .env

echo "LLDAP_KEY_SEED=$(openssl rand -hex 32)" >> .env

echo "LLDAP_LDAP_USER_PASS=$(openssl rand -hex 16)" >> .env
chmod 600 .env

Print the file once and copy the admin password into your password manager:

cat .env
  • Set the admin credentials and directory settings. Create a docker-compose.yml file in the same directory. The base DN defines the root of your directory, so replace example and com with the parts of your own domain:
services:

  lldap:

    image: lldap/lldap:stable

    container_name: lldap

    restart: unless-stopped

    ports:

      - "127.0.0.1:3890:3890"

      - "127.0.0.1:17170:17170"

    volumes:

      - lldap_data:/data

    networks:

      - auth

    environment:

      - TZ=Europe/Berlin

      - LLDAP_JWT_SECRET=${LLDAP_JWT_SECRET}

      - LLDAP_KEY_SEED=${LLDAP_KEY_SEED}

      - LLDAP_LDAP_USER_PASS=${LLDAP_LDAP_USER_PASS}

      - LLDAP_LDAP_BASE_DN=dc=example,dc=com

volumes:

  lldap_data:

networks:

  auth:

    name: auth

Both ports bind to localhost, which keeps lldap off the public internet. The named auth network lets containers from other Compose projects reach lldap later. The container only reads the admin password on its first start. After that, you change it through the web interface.

Starting lldap and Logging In

  • Run the container. Start the stack in the background:
docker compose up -d

Then check the logs for errors. Press Ctrl+C to leave the log view.

docker compose logs -f lldap
  • Log in to the web interface. The port only listens on localhost, so open an SSH tunnel from your own computer:
ssh -L 17170:localhost:17170 user@your-server-ip

Keep the tunnel open and browse to http://localhost:17170 on your computer. Sign in as admin with the password from your .env file.

The tunnel works well for occasional admin tasks. If you manage users often, publish the web interface through a reverse proxy with HTTPS instead. Protect that route with strong credentials, because it controls access to every connected app.

Adding Users and Groups

  • Add users and groups. In the web interface, create groups that match your access rules, such as family or admins. Then create users and assign them to those groups. Also create a dedicated service account, for example bind_user. Add it to the lldap_strict_readonly group. Apps use this account to search the directory, so they never need your admin password.

Plan your groups around access, not around people. A group per app, such as wiki_users or media_users, keeps permissions clear. A separate admins group can then unlock admin rights inside each app. If you need extra profile fields, lldap lets you define custom user attributes in the web interface.

Next, test the directory from the server. Install the LDAP client tools first:

sudo apt install -y ldap-utils

Then query all users. Enter the admin password at the prompt:

ldapsearch -x -H ldap://localhost:3890 \

  -D "uid=admin,ou=people,dc=example,dc=com" -W \

  -b "ou=people,dc=example,dc=com" "(objectClass=person)" uid mail

The output should list every user you created, along with their email addresses.

Enabling Password Resets

By default, only an admin can change a forgotten password. lldap can also send reset links by email once you give it SMTP access. Add these variables to the environment section of your Compose file and adjust them to your mail provider:

- LLDAP_HTTP_URL=https://ldap.example.com

      - LLDAP_SMTP_OPTIONS__ENABLE_PASSWORD_RESET=true

      - LLDAP_SMTP_OPTIONS__SERVER=smtp.example.com

      - LLDAP_SMTP_OPTIONS__PORT=587

      - LLDAP_SMTP_OPTIONS__SMTP_ENCRYPTION=STARTTLS

      - [email protected]

      - LLDAP_SMTP_OPTIONS__PASSWORD=your_smtp_password

      - LLDAP_SMTP_OPTIONS__FROM=lldap <[email protected]>

The HTTP URL tells lldap which address to put into the reset links. It only makes sense when users can reach the web interface, for example through the reverse proxy mentioned above. Recreate the container to apply the changes:

docker compose up -d

Backing Up lldap

Your whole directory lives in a small SQLite database inside the lldap_data volume. Losing it means losing every account, so back it up regularly. Docker prefixes volume names with the project name, which matches the directory name. With the setup above, Docker names the volume lldap_lldap_data. The following command packs it into an archive in your current directory:

docker run --rm -v lldap_lldap_data:/data -v "$PWD":/backup alpine tar czf /backup/lldap-backup.tar.gz -C /data .

Store the archive away from the server, and keep a copy of your .env file with it. The key seed in that file belongs to the database, so you need both to restore a working directory.

Connecting Other Services to lldap

Once lldap holds your users, each app needs to know where to find them.

The Settings Every Service Needs

Most self-hosted apps offer an LDAP section in their admin settings. The field names vary, but the values follow the same pattern. lldap places users under ou=people and groups under ou=groups, below your base DN. A typical configuration looks like this:

LDAP URL:         ldap://lldap:3890

Base DN:          dc=example,dc=com

Bind DN:          uid=bind_user,ou=people,dc=example,dc=com

Bind password:    password of bind_user

User base DN:     ou=people,dc=example,dc=com

User filter:      (&(objectClass=person)(uid={username}))

Login attribute:  uid

Group base DN:    ou=groups,dc=example,dc=com

Group filter:     (objectClass=groupOfUniqueNames)

The {username} placeholder differs between apps. Some use %s, others use %u or {user}. Check the documentation of each service for its exact syntax.

After you save the settings, test the login with a regular user account rather than admin. Many apps also offer a test button that runs a sample search with the bind account. If the search fails, check the bind DN and password first, since typos there cause most connection errors.

To restrict an app to certain users, filter on group membership. lldap supports group checks through the memberOf attribute. The following filter admits only members of one group:

(&(objectClass=person)(uid={username})(memberOf=cn=app_users,ou=groups,dc=example,dc=com))

How Apps Reach lldap

Containers reach lldap through the shared auth network. Each app stack declares that network as external and attaches its service to it. A minimal example looks like this:

services:

  myapp:

    image: example/app:latest

    networks:

      - auth

networks:

  auth:

    external: true

The app then connects to lldap:3890 by service name. Apps installed directly on the host use the localhost port instead. Neither path exposes LDAP to the internet.

Some apps read users from LDAP and create local accounts on first login. Others query lldap on every login and never store passwords themselves. Either way, a password change in lldap applies everywhere on the next login. Disabled or deleted users lose access the same way, which keeps offboarding to a single click.

Apps without LDAP support can still benefit. An authentication proxy such as Authelia or Authentik sits in front of them and checks credentials against lldap. If a service runs on a different server, never send plain LDAP over the internet. Enable LDAPS on port 6360, or connect the servers through a private network.

Hosting lldap on a Contabo VPS

lldap itself barely registers on a server. Even Contabo’s smallest Core VPS plan gives it far more than it will ever use. That entry plan, Cloud VPS 4, includes 4 vCPU cores, 8 GB of RAM and 100 GB of SSD storage. lldap occupies only a tiny fraction of those resources. The rest goes to the apps it authenticates, such as an authentication proxy, a file sync service or a wiki. Contabo lists Core VPS plans from €4.40 per month, with the final price depending on your contract term. If your stack grows into databases under sustained load, the Performance VPS line adds current-generation AMD EPYC processors and NVMe storage.

FAQ: Self-Hosting lldap

Is lldap a full LDAP server replacement?

No, and it does not try to be. lldap focuses on one job: storing users and groups for authentication. It skips features like full custom schemas, replication and arbitrary directory trees. You manage entries mainly through the web interface or its GraphQL API. Most self-hosted apps only need user lookups and group checks, so they work fine. A few services need password hashes from the directory to validate logins themselves, and those remain incompatible. If you already run OpenLDAP, a migration tool helps you move users and groups across. For larger organizations with complex directory needs, OpenLDAP or FreeIPA remains the better choice. For a self-hosted stack on one VPS, lldap covers everything that matters.

Does lldap support single sign-on?

Not on its own. lldap acts as a directory, not as an identity provider. It checks usernames and passwords, but it does not issue OpenID Connect or SAML tokens. For real single sign-on, pair lldap with a tool like Authelia, Authentik or Keycloak. These tools use lldap as their user backend and handle the login flow. Users then sign in once and move between apps without new prompts. Apps that only support LDAP keep talking to lldap directly. Both paths share the same accounts, so you manage everything in one place. Many self-hosters start with plain LDAP logins and add an SSO layer later. lldap supports that path without any migration, because the user directory stays the same.

How much resource overhead does lldap add?

Very little. The developers wrote lldap in Rust, and it stores its data in SQLite by default. The project’s documentation puts its memory use below 10 MB of RAM, database included. CPU usage stays near zero outside of login bursts. That footprint makes lldap a good fit even for the smallest VPS plans. For larger setups, you can switch the backend to MySQL or PostgreSQL. Most self-hosters never need to. Disk usage stays small too, since the database only holds account data. A directory with hundreds of users fits into a few megabytes.

Share 𝕏 in