OpenLDAP is the go-to open source LDAP server when you need centralized authentication without the overhead of FreeIPA or Active Directory. If all you need is a lightweight directory for user accounts, groups, and SSH key distribution across a handful of Linux servers, OpenLDAP does the job with minimal resources. For larger environments with Kerberos, DNS, and certificate management baked in, FreeIPA or AD are better fits.
This guide walks through a clean OpenLDAP install on Ubuntu 24.04 LTS and confirms the same steps work unchanged on 22.04 and 20.04. It covers the debconf pre-seed that skips the interactive installer, slapd verification with slapcat, adding the base organizational units, creating a user with a hashed password via slappasswd, and adding a POSIX group. By the end the directory is ready for SSSD clients or for a web UI like LDAP Account Manager.
Verified working: April 2026 on Ubuntu 24.04.4 LTS (slapd 2.6.10), 22.04.5 LTS (slapd 2.5.19), and 20.04.6 LTS (slapd 2.4.49). Identical LDIF, same commands, no per-version forks.
Prerequisites
- Ubuntu 24.04, 22.04, or 20.04 LTS server with root or sudo access
- A static IP address on the server (DHCP is fine for a lab; static is mandatory in production)
- A fully qualified domain name for the server, for example
ldap.example.com - TCP/389 (LDAP) and TCP/636 (LDAPS) open to clients that will query the directory
Set the server hostname
Set the FQDN first so slapd and any self-signed TLS certs generated later use the correct name. Replace the IP with your actual server IP:
sudo hostnamectl set-hostname ldap.example.com
echo "10.0.1.50 ldap.example.com ldap" | sudo tee -a /etc/hosts
Confirm the FQDN resolves locally:
hostname -f
The output should echo ldap.example.com. If it does not, the hosts entry is missing or the hostname command did not take effect.
Install slapd non-interactively
The slapd package ships OpenLDAP’s standalone daemon and its config backend (cn=config). ldap-utils provides ldapsearch, ldapadd, ldapmodify, and friends. Pre-seed debconf so the installer skips its interactive prompts and uses the domain, organization, and admin password you want. Replace the password placeholder with a strong value before running:
sudo debconf-set-selections <<< "slapd slapd/internal/adminpw password StrongAdminPassword"
sudo debconf-set-selections <<< "slapd slapd/password1 password StrongAdminPassword"
sudo debconf-set-selections <<< "slapd slapd/password2 password StrongAdminPassword"
sudo debconf-set-selections <<< "slapd slapd/domain string example.com"
sudo debconf-set-selections <<< "slapd shared/organization string Example Inc"
sudo debconf-set-selections <<< "slapd slapd/backend string MDB"
sudo debconf-set-selections <<< "slapd slapd/no_configuration boolean false"
Install the packages with DEBIAN_FRONTEND=noninteractive so apt honors the pre-seeded values:
sudo apt update
sudo DEBIAN_FRONTEND=noninteractive apt install -y slapd ldap-utils
Earlier versions of this guide recommended running dpkg-reconfigure -f noninteractive slapd after installing, claiming the initial install sometimes defaulted the base DN to the hostname. That step is a trap: on current Ubuntu releases the debconf pre-seed is honored correctly the first time, and dpkg-reconfigure -f noninteractive slapd silently drops back to interactive-only questions (including the admin password), leaving you with a directory you cannot bind to. Skip it.
Enable the service and check its state:
sudo systemctl enable --now slapd
systemctl status slapd --no-pager
The unit should show active (running). slapd listens on TCP/389 by default, plus TCP/636 once LDAPS is configured. If UFW is active, open the ports now:
sudo ufw allow 389/tcp comment 'OpenLDAP'
sudo ufw allow 636/tcp comment 'OpenLDAPS'
Verify the base DN and admin bind
Use slapcat to dump the backend directly. It bypasses the network and the ACLs, so it works even if slapd has misconfigured listeners:
sudo slapcat
The first entry should be the base DN:
dn: dc=example,dc=com
objectClass: top
objectClass: dcObject
objectClass: organization
o: Example Inc
dc: example
Confirm that the admin bind DN authenticates with the password you seeded. ldapwhoami is the fastest smoke test because it returns the authenticated DN on success and a numeric error on failure:
ldapwhoami -x -D "cn=admin,dc=example,dc=com" -W
The -W flag prompts for the password instead of passing it on the command line (which lands in shell history). On success:
dn:cn=admin,dc=example,dc=com
If it returns ldap_bind: Invalid credentials (49), the debconf pre-seed did not take effect. Purge and reinstall with the pre-seed in the same shell session:
sudo apt purge -y slapd
sudo rm -rf /etc/ldap/slapd.d /var/lib/ldap
# then re-run the debconf-set-selections block and apt install
Add the base organizational units
Every LDAP directory wants at least two OUs: one for people, one for groups. Keeping them separate makes access rules and search bases much simpler later. Write an LDIF file with both:
sudo tee /tmp/basedn.ldif >/dev/null <<'LDIF'
dn: ou=people,dc=example,dc=com
objectClass: organizationalUnit
ou: people
dn: ou=groups,dc=example,dc=com
objectClass: organizationalUnit
ou: groups
LDIF
Import it with ldapadd. The -D is the bind DN, -W prompts for the password, and -f points at the LDIF:
ldapadd -x -D "cn=admin,dc=example,dc=com" -W -f /tmp/basedn.ldif
You should see both entries accepted:
adding new entry "ou=people,dc=example,dc=com"
adding new entry "ou=groups,dc=example,dc=com"
Add a user account
LDAP user accounts for Linux login combine three object classes: inetOrgPerson (for name attributes), posixAccount (for UID/GID/shell/homeDirectory), and shadowAccount (for password aging fields). Generate the password hash first; slappasswd produces a salted SSHA hash by default:
slappasswd
Enter and confirm the user’s password. The output is a hash like:
{SSHA}W3ipFKHeFr6bR7a0r1WgOJlhbkRbMQPl
Copy that exact string. Now build the user LDIF, pasting the hash into userPassword verbatim:
sudo tee /tmp/user.ldif >/dev/null <<'LDIF'
dn: uid=jdoe,ou=people,dc=example,dc=com
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
cn: John Doe
sn: Doe
uid: jdoe
uidNumber: 10001
gidNumber: 10001
homeDirectory: /home/jdoe
loginShell: /bin/bash
userPassword: {SSHA}W3ipFKHeFr6bR7a0r1WgOJlhbkRbMQPl
LDIF
Start LDAP UIDs and GIDs at 10000 or higher. Anything below 1000 collides with system accounts, and 1000 to 9999 is the range most distros hand out to locally-created users via adduser.
Import the entry:
ldapadd -x -D "cn=admin,dc=example,dc=com" -W -f /tmp/user.ldif
Expected output:
adding new entry "uid=jdoe,ou=people,dc=example,dc=com"
Add a group (POSIX and SSSD-compatible)
There are two ways to model groups in LDAP, and which one you pick matters as soon as a client talks to the directory. The posixGroup class with memberUid attributes is the traditional rfc2307 style; it maps cleanly to Linux groups and getent group. The groupOfNames class with full-DN member attributes is the rfc2307bis style that the memberOf overlay needs, which is how modern SSSD clients filter logins by group. For a future-proof setup that works with both, include both object classes and both attributes:
sudo tee /tmp/group.ldif >/dev/null <<'LDIF'
dn: cn=developers,ou=groups,dc=example,dc=com
objectClass: groupOfNames
objectClass: posixGroup
cn: developers
gidNumber: 10001
member: uid=jdoe,ou=people,dc=example,dc=com
memberUid: jdoe
LDIF
On default Ubuntu schemas, posixGroup is declared STRUCTURAL, which conflicts with groupOfNames (also structural). The LDIF above will be rejected with an object-class-chain error. Patch posixGroup to AUXILIARY once, then imports work cleanly:
sudo tee /tmp/posixgroup-aux.ldif >/dev/null <<'LDIF'
dn: cn={2}nis,cn=schema,cn=config
changetype: modify
delete: olcObjectClasses
olcObjectClasses: {2}( 1.3.6.1.1.1.2.2 NAME 'posixGroup' DESC 'Abstraction of a group of accounts' SUP top STRUCTURAL MUST ( cn $ gidNumber ) MAY ( userPassword $ memberUid $ description ) )
-
add: olcObjectClasses
olcObjectClasses: {2}( 1.3.6.1.1.1.2.2 NAME 'posixGroup' DESC 'Abstraction of a group of accounts' SUP top AUXILIARY MUST gidNumber MAY ( userPassword $ memberUid $ description ) )
LDIF
sudo ldapmodify -Y EXTERNAL -H ldapi:/// -f /tmp/posixgroup-aux.ldif
Now import the group:
ldapadd -x -D "cn=admin,dc=example,dc=com" -W -f /tmp/group.ldif
If you only plan to use classic rfc2307 clients and never need memberOf, drop the groupOfNames object class and the member attribute from /tmp/group.ldif and skip the schema patch. Everything else in this guide still works.
Verify the directory
Run a few ldapsearch queries to confirm the entries exist and are returned over the wire. The query by uid=jdoe proves both the user’s attributes and the filter syntax:
ldapsearch -x -LLL -b "dc=example,dc=com" "(uid=jdoe)" cn uid uidNumber homeDirectory
Expected output (the -LLL flag strips the LDIF version and comments for a cleaner view):
dn: uid=jdoe,ou=people,dc=example,dc=com
cn: John Doe
uid: jdoe
uidNumber: 10001
homeDirectory: /home/jdoe
Query the group to confirm both membership attributes are present:
ldapsearch -x -LLL -b "dc=example,dc=com" "(cn=developers)" cn memberUid member gidNumber
Test user-level authentication with ldapwhoami. This is the same bind that a client like SSSD or a web app will perform when the user logs in:
ldapwhoami -x -D "uid=jdoe,ou=people,dc=example,dc=com" -W
A successful bind returns dn:uid=jdoe,ou=people,dc=example,dc=com. If it returns Invalid credentials (49), the userPassword hash in the LDIF did not match what you typed into slappasswd; generate a new hash and use ldapmodify to replace the attribute.
Ubuntu version notes
The steps above produce identical results on the three current LTS releases, but the packaged slapd versions differ enough that a few version-specific gotchas are worth knowing:
| Item | Ubuntu 24.04 | Ubuntu 22.04 | Ubuntu 20.04 |
|---|---|---|---|
| slapd version | 2.6.10 | 2.5.19 | 2.4.49 |
| Default backend | MDB | MDB | MDB |
| Config path | /etc/ldap/slapd.d | /etc/ldap/slapd.d | /etc/ldap/slapd.d |
| Data path | /var/lib/ldap | /var/lib/ldap | /var/lib/ldap |
| systemd unit | slapd.service | slapd.service | slapd.service |
| Notable schema change | posixGroup still STRUCTURAL (same patch needed) | posixGroup still STRUCTURAL (same patch needed) | posixGroup still STRUCTURAL (same patch needed) |
One real cross-version gotcha on 20.04: the slapcat output sometimes includes the admin entry (cn=admin,dc=example,dc=com) in the initial dump where newer releases only show the base DN. That is cosmetic, not a misconfiguration, and both variants bind identically.
Next steps
The server is ready for clients. To wire up Linux hosts, see the companion walkthrough for configuring Ubuntu as an LDAP client with SSSD, which reuses the same directory and adds group-filtered logins plus sudo for an admin group. Before pointing real users at this server, move off port 389 and serve LDAPS with a real cert: the walkthrough for securing OpenLDAP with TLS on Ubuntu covers cert generation and the olcTLS* attributes slapd needs.
If you would rather manage users through a browser than write LDIF files, install LDAP Account Manager on Ubuntu. It is especially useful for delegating day-to-day user management to team leads. For environments where OpenLDAP is outgrown, FreeIPA on Rocky or AlmaLinux bundles Kerberos, DNS, certificate authority, and host-based access control in one stack.
Four other extensions worth looking at once the basic directory is in place:
- Password policy: load the
ppolicyoverlay to enforce complexity, expiry, and lockout on failed binds. - Replication: configure
syncreplbetween two slapd nodes so the directory survives a host failure. - SSH key distribution: store
sshPublicKeyvalues under each user and wiresshdto fetch them viaAuthorizedKeysCommand. - Backups: schedule a daily
slapcatand keep the LDIF offsite. Restores are justslapadd.
What is this password when I have to add the basedn.ldif?
The one asked while running
sudo apt -y install slapd ldap-utilssudo – superuser do would usually require the login of the user loged in at that time. If that user does not have su permissions, then you would need to log in with one. Last resort would be to use root.
Hope that helps.
“Create a file named basedn.ldif”
Where should this file be created? The article does not say.
“$ ldapadd -x -D cn=admin,dc=example,dc=com -W -f basedn.ldif
Enter LDAP Password:”
Article does not state which password should be used.
It is not the Admin password created above this step, nor is it the user password, nor is it the root password.
Sorry to say, but I am giving up on this Article and will find the info elsewhere.
The lack of specificity and the ambiguity lead me to believe that this article is badly written and was not proof read before it was published.
Great tutorial.
I want to have users authenticate against openLDAP, but at the same time I also want to integrate openLDAP with MS Active Directory as the database. Is this possible? If so,
could you please provide me the steps-by-steps instructions on how to do this?