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
ldapsearchand note the classes and attributes Keycloak will need to know.
Prerequisites
- An Ubuntu 24.04 machine (or an equivalent Debian) with
sudoaccess. - The notions of DN, bind and base DN, explained in LDAP, Active Directory and Kerberos (in French).
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
ldapsearchor 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.
-
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
-
-
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 throughSigned-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/keyringscurl -fsSL https://download.opensuse.org/repositories/home:/Masgalor:/LLDAP/xUbuntu_24.04/Release.key | \gpg --dearmor | sudo tee /etc/apt/keyrings/lldap.gpg > /dev/nullsudo tee /etc/apt/sources.list.d/lldap.sources > /dev/null <<'EOF'Types: debURIs: https://download.opensuse.org/repositories/home:/Masgalor:/LLDAP/xUbuntu_24.04/Suites: /Signed-By: /etc/apt/keyrings/lldap.gpgEOFsudo apt updateapt-cache policy lldapsudo apt install lldapOn Debian 13, replace
xUbuntu_24.04withDebian_13in both URLs. Before installing,apt-cache policy lldapmust show aCandidatecoming fromdownload.opensuse.org: otherwise the source is not being picked up. -
Once the installation is finished, check that LLDAP is correctly installed with:
Terminal window lldap --versionYou 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:
-
Generate the TLS certificate for your LDAP server:
Terminal window cd ~mkcert 'ldap.robert.local' localhost 127.0.0.1 ::1 -
Install the certificate in a directory owned by the LLDAP user:
Terminal window sudo mkdir /etc/lldap/certssudo mv ~/ldap.robert.local+3-key.pem /etc/lldap/certs/lldap.keysudo mv ~/ldap.robert.local+3.pem /etc/lldap/certs/lldap.pemsudo 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_SEEDkey_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:
sudo systemctl enable lldap --nowYou can check that the server is running with:
sudo systemctl status lldapYou can also read the logs to see the startup messages:
journalctl -u lldapCheck that the server is listening on ports 3890 (LDAP), 6360 (LDAPS) and 17170 (web interface):
sudo ss -tulnp | grep lldaptcp 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.

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:
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.comdn: uid=admin,ou=people,dc=example,dc=comcn: Administratorcreatetimestamp: 2025-01-27T13:56:43.252453685+00:00entryuuid: 666964a9-a2d8-36ca-a19d-6149e3a5aac6mail: admin@example.comobjectclass: inetOrgPersonobjectclass: posixAccountobjectclass: mailAccountobjectclass: personuid: admin
# lldap_admin, groups, example.comdn: cn=lldap_admin,ou=groups,dc=example,dc=comcn: lldap_adminentryuuid: 2dbd571f-e20a-3195-ad85-51fa1232e20cmember: uid=admin,ou=people,dc=example,dc=comobjectclass: groupOfUniqueNamesuid: lldap_adminuniquemember: uid=admin,ou=people,dc=example,dc=com
# lldap_password_manager, groups, example.comdn: cn=lldap_password_manager,ou=groups,dc=example,dc=comcn: lldap_password_managerentryuuid: 094c5f0d-2a80-3192-935c-38f3912fd41dobjectclass: groupOfUniqueNamesuid: lldap_password_manager
# lldap_strict_readonly, groups, example.comdn: cn=lldap_strict_readonly,ou=groups,dc=example,dc=comcn: lldap_strict_readonlyentryuuid: c37db72c-e43c-3ca4-a150-97484638d1c1objectclass: groupOfUniqueNamesuid: lldap_strict_readonly
# search resultsearch: 2result: 0 Successcontrol: 1.2.840.113556.1.4.319 false MAUCAQQEAA==pagedresults: estimate=4 cookie=
# numResponses: 5# numEntries: 4The 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 shows | Observed value |
|---|---|
| Users | stored under ou=people, with the uid attribute (for example uid=admin) |
| A user's object classes | inetOrgPerson, posixAccount, mailAccount, person |
| A user's attributes | uid, cn, mail, entryuuid (LDAP is case-insensitive for attribute names) |
| Groups | stored under ou=groups, of class groupOfUniqueNames |
| A group's member attributes | member and uniquemember are both present on lldap_admin |
| Listening ports | 3890 (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 isuid=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.