Skip to content
Français
Account: not signed in, sync available My learning
Select theme
Français
Lire en français

Set up a lightweight LDAP directory with LLDAP

26 min read
Improve this page

This lesson sets up LLDAP, a lightweight LDAP directory server, and checks with ldapsearch that it answers. It is the test directory of the "Federation and directories" module: you install it here, then Keycloak connects to it (in French). The LDAP protocol (Lightweight Directory Access Protocol) is used to centralize accounts and share them between services, but OpenLDAP or 389 Directory Server demand heavy configuration. LLDAP sticks to the essentials, for small teams and labs.

What you will learn

  • Place LLDAP against OpenLDAP and Active Directory.
  • Install LLDAP on Ubuntu 24.04 and generate a test TLS certificate.
  • Configure the server (base DN, admin account, LDAPS) and start it.
  • Query the directory with ldapsearch and note the classes and attributes Keycloak will need to know.

Prerequisites

Why LLDAP rather than a classic LDAP server?

The LDAP protocol was designed in the 1990s to centralize users, groups and access rights. Servers such as OpenLDAP or Microsoft Active Directory still meet the needs of large companies, but their complexity is out of proportion for a small team that just wants a directory for a few users.

LLDAP was born from this observation: offer a lightweight, fast directory that is easy to set up. With the adoption of containers and microservices, a modern LDAP server was needed, one that deploys in a few minutes and plugs easily into identity providers such as Keycloak. Its approach is minimalist, in the spirit of the KISS principle (Keep It Simple, Stupid): it does what you need, without the surplus.

LLDAP features

LLDAP does not try to reinvent the wheel, but to simplify its use: a lightweight, modern solution for managing LDAP directories, which keeps the standards of the protocol. Here is what makes it an interesting choice:

  • Lightweight and fast: With a backend based on SQLite, LLDAP is optimized to be light while staying fast. No need for a massive infrastructure to run it: it can run on a small machine or a container with minimal resources.
  • Easy to deploy and configure: Unlike heavier solutions such as OpenLDAP, LLDAP shines through its ease of setup. Whether you install it locally or through a Docker or Podman container, everything is designed to be up and running in a few minutes. No more hours spent wading through manuals or endless configuration files!
  • Built-in web interface: This is a real strength: LLDAP offers a simple, intuitive web interface to manage your users and groups. No need to know LDAP commands by heart or to edit files by hand. Everything is done in a browser with a few clicks.
  • TLS encryption: LLDAP supports TLS (LDAPS) to encrypt communications. Its setup is described below, along with the limit met during testing.
  • Compatibility with standard LDAP tools: Even though LLDAP is designed for simplicity, it stays compatible with classic LDAP tools and libraries. You can keep using commands such as ldapsearch or Python libraries to interact with it.

In short, LLDAP brings the essentials of an LDAP server without its administration complexity, which suits a small team or a homelab.

Quick install of LLDAP (without a container)

LLDAP is installed directly on the system from a package repository: this container-free installation keeps the example readable. It targets Ubuntu 24.04, whose repository is used in step 2; on Debian, adapt the repository URL to your version.

  1. Installing the prerequisites: Before starting, make sure you have the following:

    • SQLite3: LLDAP uses SQLite as its default backend, so make sure it is installed. For example, on Debian/Ubuntu:

      Terminal window
      sudo apt update && sudo apt install sqlite3 libsqlite3-dev
    • mkcert: To generate self-signed TLS certificates, you will need mkcert. Install it with:

      Terminal window
      sudo apt install mkcert
    • the LDAP tools: To test your LDAP server, you will need ldapsearch. Install it with:

      Terminal window
      sudo apt install ldap-utils
  2. Installing LLDAP: once the prerequisites are in place, declare the package repository, then install LLDAP. The repository's signing key goes in /etc/apt/keyrings/, and the source references it through Signed-By: APT then grants this key to that repository only, whereas a key dropped in /etc/apt/trusted.gpg.d/ would be accepted for every repository on the system. The repository is served over HTTPS.

    Terminal window
    sudo install -d -m 0755 /etc/apt/keyrings
    curl -fsSL https://download.opensuse.org/repositories/home:/Masgalor:/LLDAP/xUbuntu_24.04/Release.key | \
    gpg --dearmor | sudo tee /etc/apt/keyrings/lldap.gpg > /dev/null
    sudo tee /etc/apt/sources.list.d/lldap.sources > /dev/null <<'EOF'
    Types: deb
    URIs: https://download.opensuse.org/repositories/home:/Masgalor:/LLDAP/xUbuntu_24.04/
    Suites: /
    Signed-By: /etc/apt/keyrings/lldap.gpg
    EOF
    sudo apt update
    apt-cache policy lldap
    sudo apt install lldap

    On Debian 13, replace xUbuntu_24.04 with Debian_13 in both URLs. Before installing, apt-cache policy lldap must show a Candidate coming from download.opensuse.org: otherwise the source is not being picked up.

  3. Once the installation is finished, check that LLDAP is correctly installed with:

    Terminal window
    lldap --version

    You should see a version displayed, confirming that everything is in place.

Generating the TLS certificate

To encrypt communications with your LDAP server, generate a self-signed TLS certificate with mkcert, the tool installed in step 1. Its installation and configuration are detailed in this article on mkcert (in French). The name ldap.robert.local is the lab's: replace it with your own.

Here is how to proceed:

  1. Generate the TLS certificate for your LDAP server:

    Terminal window
    cd ~
    mkcert 'ldap.robert.local' localhost 127.0.0.1 ::1
  2. Install the certificate in a directory owned by the LLDAP user:

    Terminal window
    sudo mkdir /etc/lldap/certs
    sudo mv ~/ldap.robert.local+3-key.pem /etc/lldap/certs/lldap.key
    sudo mv ~/ldap.robert.local+3.pem /etc/lldap/certs/lldap.pem
    sudo chown -R lldap:lldap /etc/lldap/certs

Configuring LLDAP

LLDAP's configuration file is in TOML format and holds several parameters to customize the server. Here is the lab's:

## Tune the logging to be more verbose by setting this to be true.
## You can set it with the LLDAP_VERBOSE environment variable.
# verbose=false
## The host address that the LDAP server will be bound to.
## To enable IPv6 support, simply switch "ldap_host" to "::":
## To only allow connections from localhost (if you want to restrict to local self-hosted services),
## change it to "127.0.0.1" ("::1" in case of IPv6)".
#ldap_host = "0.0.0.0"
## The port on which to have the LDAP server.
#ldap_port = 3890
## The host address that the HTTP server will be bound to.
## To enable IPv6 support, simply switch "http_host" to "::".
## To only allow connections from localhost (if you want to restrict to local self-hosted services),
## change it to "127.0.0.1" ("::1" in case of IPv6)".
#http_host = "0.0.0.0"
## The port on which to have the HTTP server, for user login and
## administration.
#http_port = 17170
## The public URL of the server, for password reset links.
#http_url = "http://localhost"
## Random secret for JWT signature.
## This secret should be random, and should be shared with application
## servers that need to consume the JWTs.
## Changing this secret will invalidate all user sessions and require
## them to re-login.
## You should probably set it through the LLDAP_JWT_SECRET environment
## variable from a secret ".env" file.
## This can also be set from a file's contents by specifying the file path
## in the LLDAP_JWT_SECRET_FILE environment variable
## You can generate it with (on linux):
## LC_ALL=C tr -dc 'A-Za-z0-9!#%&'\''()*+,-./:;<=>?@[\]^_{|}~' </dev/urandom | head -c 32; echo ''
#jwt_secret = "REPLACE_WITH_RANDOM"
## Base DN for LDAP.
## This is usually your domain name, and is used as a
## namespace for your users. The choice is arbitrary, but will be needed
## to configure the LDAP integration with other services.
## The sample value is for "example.com", but you can extend it with as
## many "dc" as you want, and you don't actually need to own the domain
## name.
ldap_base_dn = "dc=robert,dc=local"
## Admin username.
## For the LDAP interface, a value of "admin" here will create the LDAP
## user "cn=admin,ou=people,dc=example,dc=com" (with the base DN above).
## For the administration interface, this is the username.
ldap_user_dn = "admin"
## Admin email.
## Email for the admin account. It is only used when initially creating
## the admin user, and can safely be omitted.
ldap_user_email = "admin@example.com"
## Admin password.
## Password for the admin account, both for the LDAP bind and for the
## administration interface. It is only used when initially creating
## the admin user.
## It should be minimum 8 characters long.
## You can set it with the LLDAP_LDAP_USER_PASS environment variable.
## This can also be set from a file's contents by specifying the file path
## in the LLDAP_LDAP_USER_PASS_FILE environment variable
## Note: you can create another admin user for user administration, this
## is just the default one.
ldap_user_pass = "changeme"
## Force reset of the admin password.
## Break glass in case of emergency: if you lost the admin password, you
## can set this to true to force a reset of the admin password to the value
## of ldap_user_pass above.
## Alternatively, you can set it to "always" to reset every time the server starts.
force_ldap_user_pass_reset = false
## Database URL.
## This encodes the type of database (SQlite, MySQL, or PostgreSQL)
## , the path, the user, password, and sometimes the mode (when
## relevant).
## Note: SQlite should come with "?mode=rwc" to create the DB
## if not present.
## Example URLs:
## - "postgres://postgres-user:password@postgres-server/my-database"
## - "mysql://mysql-user:password@mysql-server/my-database"
##
## This can be overridden with the LLDAP_DATABASE_URL env variable.
database_url = "sqlite:///var/lib/lldap/users.db?mode=rwc"
## Private key file.
## Not recommended, use key_seed instead.
## Contains the secret private key used to store the passwords safely.
## Note that even with a database dump and the private key, an attacker
## would still have to perform an (expensive) brute force attack to find
## each password.
## Randomly generated on first run if it doesn't exist.
## Env variable: LLDAP_KEY_FILE
#key_file = "/var/lib/lldap/private_key"
## Seed to generate the server private key, see key_file above.
## This can be any random string, the recommendation is that it's at least 12
## characters long.
## Env variable: LLDAP_KEY_SEED
key_seed = "RanD0m STR1ng"
## Ignored attributes.
## Some services will request attributes that are not present in LLDAP. When it
## is the case, LLDAP will warn about the attribute being unknown. If you want
## to ignore the attribute and the service works without, you can add it to this
## list to silence the warning.
#ignored_user_attributes = [ "sAMAccountName" ]
#ignored_group_attributes = [ "mail", "userPrincipalName" ]
## Options to configure SMTP parameters, to send password reset emails.
## To set these options from environment variables, use the following format
## (example with "password"): LLDAP_SMTP_OPTIONS__PASSWORD
[smtp_options]
## Whether to enabled password reset via email, from LLDAP.
#enable_password_reset=true
## The SMTP server.
#server="smtp.gmail.com"
## The SMTP port.
#port=587
## How the connection is encrypted, either "NONE" (no encryption), "TLS" or "STARTTLS".
#smtp_encryption = "TLS"
## The SMTP user, usually your email address.
#user="sender@gmail.com"
## The SMTP password.
#password="password"
## The header field, optional: how the sender appears in the email. The first
## is a free-form name, followed by an email between <>.
#from="LLDAP Admin <sender@gmail.com>"
## Same for reply-to, optional.
#reply_to="Do not reply <noreply@localhost>"
## Options to configure LDAPS.
## To set these options from environment variables, use the following format
## (example with "port"): LLDAP_LDAPS_OPTIONS__PORT
[ldaps_options]
## Whether to enable LDAPS.
enabled=true
## Port on which to listen.
port=6360
## Certificate file.
cert_file="/etc/lldap/certs/lldap.pem"
## Certificate key file.
key_file="/etc/lldap/certs/lldap.key"

The values in this example are the lab's, to be replaced with your own. Four settings matter for the rest of the lesson: ldap_base_dn sets the root of the directory (dc=robert,dc=local), ldap_user_dn and ldap_user_pass define the administrator account, and the [ldaps_options] section enables LDAPS on port 6360 with the certificate generated above. Before any use outside the lab, replace changeme and the example key_seed, and uncomment jwt_secret with a random value supplied as an environment variable, never written into a Git repository.

Starting LLDAP

To start LLDAP, use the following command:

Terminal window
sudo systemctl enable lldap --now

You can check that the server is running with:

Terminal window
sudo systemctl status lldap

You can also read the logs to see the startup messages:

Terminal window
journalctl -u lldap

Check that the server is listening on ports 3890 (LDAP), 6360 (LDAPS) and 17170 (web interface):

Terminal window
sudo ss -tulnp | grep lldap
tcp LISTEN 0 2048 0.0.0.0:6360 0.0.0.0:* users:(("lldap",pid=995582,fd=11))
tcp LISTEN 0 2048 0.0.0.0:3890 0.0.0.0:* users:(("lldap",pid=995582,fd=10))
tcp LISTEN 0 2048 0.0.0.0:17170 0.0.0.0:* users:(("lldap",pid=995582,fd=12))

LLDAP's web interface is available at http://localhost:17170: log in with the administration credentials you configured (ldap_user_dn and ldap_user_pass). There you can add users and groups, and manage the server settings.

LLDAP web interface

Testing your LDAP server

To make sure LLDAP works, query the directory with ldapsearch. The options to know: -H gives the server URL (port 3890, plain LDAP), -D the DN of the account that connects (the bind), -w its password (never pass it this way outside a lab: it stays visible in the history and in the process list) and -b the search base. Note: the command below targets the dc=robert,dc=local base of this lesson, but the output that follows was captured on a directory with the default base, dc=example,dc=com, hence the gap between the DNs in the command and those in the result:

Terminal window
ldapsearch -H ldap://localhost:3890 -D "uid=admin,ou=people,dc=robert,dc=local" -w "changeme" -b "dc=robert,dc=local" "(objectClass=*)"
# extended LDIF
#
# LDAPv3
# base <dc=example,dc=com> with scope subtree
# filter: (objectClass=*)
# requesting: ALL
#
# admin, people, example.com
dn: uid=admin,ou=people,dc=example,dc=com
cn: Administrator
createtimestamp: 2025-01-27T13:56:43.252453685+00:00
entryuuid: 666964a9-a2d8-36ca-a19d-6149e3a5aac6
mail: admin@example.com
objectclass: inetOrgPerson
objectclass: posixAccount
objectclass: mailAccount
objectclass: person
uid: admin
# lldap_admin, groups, example.com
dn: cn=lldap_admin,ou=groups,dc=example,dc=com
cn: lldap_admin
entryuuid: 2dbd571f-e20a-3195-ad85-51fa1232e20c
member: uid=admin,ou=people,dc=example,dc=com
objectclass: groupOfUniqueNames
uid: lldap_admin
uniquemember: uid=admin,ou=people,dc=example,dc=com
# lldap_password_manager, groups, example.com
dn: cn=lldap_password_manager,ou=groups,dc=example,dc=com
cn: lldap_password_manager
entryuuid: 094c5f0d-2a80-3192-935c-38f3912fd41d
objectclass: groupOfUniqueNames
uid: lldap_password_manager
# lldap_strict_readonly, groups, example.com
dn: cn=lldap_strict_readonly,ou=groups,dc=example,dc=com
cn: lldap_strict_readonly
entryuuid: c37db72c-e43c-3ca4-a150-97484638d1c1
objectclass: groupOfUniqueNames
uid: lldap_strict_readonly
# search result
search: 2
result: 0 Success
control: 1.2.840.113556.1.4.319 false MAUCAQQEAA==
pagedresults: estimate=4 cookie=
# numResponses: 5
# numEntries: 4

The bind succeeds and the directory lists the administrator and three predefined groups: lldap_admin, lldap_password_manager and lldap_strict_readonly. As announced, the output comes from a directory with the default base dc=example,dc=com; with this lesson's configuration, all DNs end with dc=robert,dc=local.

LDAPS not validated in this lesson

With LLDAP 0.6.3, the LDAPS connection (port 6360) did not succeed during writing: only port 3890, in plain text, was tested with ldapsearch. A plain-text LDAP is acceptable on a lab machine. Before exposing the directory, validate LDAPS (certificate, hostname, client truststore) and do not leave port 3890 reachable from the network.

What to take from the output before connecting Keycloak?

The lesson Federating an LDAP or AD directory with Keycloak (in French) takes OpenLDAP as its example and explains each setting on the Keycloak side. It has not been replayed against LLDAP: the exact values to enter in Keycloak are therefore not given here. What this lesson establishes is what your LLDAP directory actually exposes, and the ldapsearch output above shows it:

What the output showsObserved value
Usersstored under ou=people, with the uid attribute (for example uid=admin)
A user's object classesinetOrgPerson, posixAccount, mailAccount, person
A user's attributesuid, cn, mail, entryuuid (LDAP is case-insensitive for attribute names)
Groupsstored under ou=groups, of class groupOfUniqueNames
A group's member attributesmember and uniquemember are both present on lldap_admin
Listening ports3890 (LDAP), 6360 (LDAPS), 17170 (web interface), seen in the ss output

To configure the federation provider, compare these observations with the fields described in the federation lesson (user DN, identifier attribute, UUID, object classes, groups). LLDAP groups are of class groupOfUniqueNames, whereas the federation lesson cites groupOfNames for OpenLDAP: this is a point to check when you play the integration, not a value to copy. Your directory's base DN replaces the example's.

When to choose LLDAP?

LLDAP suits small teams, labs and modest projects: installation in a few minutes, a web interface, compatibility with standard LDAP tools. It replaces neither OpenLDAP nor Active Directory in a large organization, whose functional richness it lacks. In this course, it serves as a test directory to connect to Keycloak; the federation lesson was played with OpenLDAP.

Official documentation

The reference remains the project's repository: its README describes the TOML file's configuration options, the equivalent environment variables and the integrations tested by the community with other services.

Key takeaways

  • LLDAP is a lightweight LDAP server with a web interface, suited to small teams and labs.
  • The root of the directory comes from ldap_base_dn; the administrator account is uid=admin,ou=people,<base DN>.
  • The default ports are 3890 (LDAP), 6360 (LDAPS) and 17170 (web interface).
  • The example values (changeme, key_seed, jwt_secret) must be replaced before any use outside a lab.
  • LDAPS was not validated here: in a lab, plain LDAP on 3890, on an isolated machine.
  • In the observed output, LLDAP groups are of class groupOfUniqueNames; the Keycloak-side settings are in the federation lesson, which was not replayed against LLDAP.

Is this project useful to you?

The guides, courses, quizzes and labs stay free. If you use them regularly, you can contribute to their upkeep and development.

Become a supporting member (new tab) One-time support (new tab) Learn more

Follow the project on LinkedIn