Last modified: June 06, 2026
This article is written in: 🇺🇸
LDAP stands for Lightweight Directory Access Protocol.
It is a network protocol used to access and manage directory information. A directory is a structured store of information about users, groups, devices, applications, permissions, and organizational resources.
LDAP is commonly used for centralized authentication.
Instead of creating separate user accounts on every server, an organization can store users in one LDAP directory. Then many systems can ask the LDAP server to verify users.
Without LDAP:
With LDAP:
LDAP is used by systems such as:
LDAP itself is a protocol. OpenLDAP is a common open-source LDAP server implementation.
LDAP is useful when many systems need the same identity information.
For example, an organization may have:
All of these systems may need to know:
LDAP allows this information to live in one central directory.
+--------------------+
| LDAP Server |
| ldap.example.com |
+----------+---------+
|
---------------------------------
| | |
+-------+-----+ +-----+-------+ +---+-------+
| Web Server | | Email Server| | SSH Server|
+-------------+ +-------------+ +-----------+
| | |
+---------------+---------------+
|
v
Users authenticate using
centralized LDAP data
The main benefit is consistency. If a user changes their password or leaves the organization, the change can be made centrally.
An LDAP directory is similar to a database, but it is optimized for reading, searching, and browsing structured information.
A normal relational database is often used for frequent transactions, joins, and complex updates.
An LDAP directory is usually optimized for:
A directory stores entries such as:
Each entry has attributes.
An entry is one object in the LDAP directory.
Examples of entries:
Each entry contains attributes.
For example, a user entry might contain:
uid: jdoe
cn: John Doe
sn: Doe
mail: jdoe@example.com
loginShell: /bin/bash
homeDirectory: /home/jdoe
The entry is the object. The attributes describe the object.
Entry:
uid=jdoe,ou=users,dc=example,dc=com
Attributes:
uid: jdoe
cn: John Doe
sn: Doe
mail: jdoe@example.com
A Distinguished Name, or DN, uniquely identifies an entry in the LDAP directory.
It works like a full path to the entry.
Example:
uid=jdoe,ou=users,dc=example,dc=com
This DN means:
The DN is read from left to right as specific to general.
A helpful comparison:
Filesystem path:
/home/users/jdoe
LDAP DN:
uid=jdoe,ou=users,dc=example,dc=com
Both describe where something is located in a hierarchy.
Examples:
Meaning:
LDAP stores entries in a hierarchy called the Directory Information Tree, or DIT.
(Root)
|
+----------+----------+
| |
dc=com dc=org
| |
+-----+-----+ |
| | |
dc=example dc=company ...
|
+---+---+
| |
ou=users ou=groups
| |
| +----------------+
| |
+--+--+ +---+---+
| | | |
uid=alice uid=bob cn=admins cn=users
In this example:
The base DN is the starting point for LDAP searches.
For the domain example.com, the base DN is often:
dc=example,dc=com
A search using this base can search everything under the example.com directory tree.
A narrower search base might be:
ou=users,dc=example,dc=com
This searches only under the users organizational unit.
An LDAP schema defines what kinds of entries are allowed and what attributes they can contain.
The schema controls:
An object class defines a type of entry.
Examples:
For example, inetOrgPerson is commonly used for user information such as names and email addresses.
For Linux login accounts, user entries often also need posixAccount and sometimes shadowAccount.
That is important because a simple address-book-style LDAP user is not always enough for Linux login.
A basic informational user might look like this:
dn: uid=jdoe,ou=users,dc=example,dc=com
objectClass: inetOrgPerson
uid: jdoe
cn: John Doe
sn: Doe
givenName: John
mail: jdoe@example.com
userPassword: {SSHA}encrypted_password_here
For Linux authentication through NSS/PAM, a more complete user entry usually needs POSIX attributes:
dn: uid=jdoe,ou=users,dc=example,dc=com
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
uid: jdoe
cn: John Doe
sn: Doe
givenName: John
mail: jdoe@example.com
uidNumber: 10000
gidNumber: 10000
homeDirectory: /home/jdoe
loginShell: /bin/bash
userPassword: {SSHA}encrypted_password_here
Important fields:
Without POSIX attributes, getent passwd jdoe may not return a valid Linux account.
A Linux-compatible group can use posixGroup.
dn: cn=developers,ou=groups,dc=example,dc=com
objectClass: posixGroup
cn: developers
gidNumber: 10000
memberUid: jdoe
This defines a group named developers.
The user jdoe is listed as a member using memberUid.
LDIF stands for LDAP Data Interchange Format.
It is a plain text format used to add, modify, delete, export, and import LDAP entries.
Example LDIF entry:
dn: ou=users,dc=example,dc=com
objectClass: organizationalUnit
ou: users
LDIF files are commonly used with commands such as:
LDAP supports several common operations.
Bind means authenticate to the LDAP server.
An anonymous bind does not provide a username or password.
An authenticated bind provides a DN and password.
Example:
ldapwhoami -x -D "uid=jdoe,ou=users,dc=example,dc=com" -W
Options:
Example output:
Enter LDAP Password:
dn:uid=jdoe,ou=users,dc=example,dc=com
Interpretation:
Search retrieves entries matching a filter.
Example:
ldapsearch -x -b "dc=example,dc=com" "(uid=jdoe)"
Options:
Example output:
dn: uid=jdoe,ou=users,dc=example,dc=com
uid: jdoe
cn: John Doe
sn: Doe
mail: jdoe@example.com
## search result
result: 0 Success
## numEntries: 1
Interpretation:
The add operation creates a new entry.
Example:
ldapadd -x -D "cn=admin,dc=example,dc=com" -W -f user.ldif
Example output:
adding new entry "uid=jdoe,ou=users,dc=example,dc=com"
Interpretation:
The modify operation changes an existing entry.
Example modify file:
dn: uid=jdoe,ou=users,dc=example,dc=com
changetype: modify
replace: mail
mail: john.doe@example.com
Apply it:
ldapmodify -x -D "cn=admin,dc=example,dc=com" -W -f modify_jdoe.ldif
Example output:
modifying entry "uid=jdoe,ou=users,dc=example,dc=com"
Interpretation:
The delete operation removes an entry.
Example:
ldapdelete -x -D "cn=admin,dc=example,dc=com" -W \
"uid=jdoe,ou=users,dc=example,dc=com"
Example output may be silent if successful.
Interpretation:
Search filters control which entries are returned.
Basic equality filter:
(uid=jdoe)
Find entries with an email address:
(mail=*)
AND filter:
(&(objectClass=person)(mail=*))
OR filter:
(|(uid=alice)(uid=bob))
NOT filter:
(!(uid=jdoe))
Find users with usernames beginning with a:
(uid=a*)
A search filter is one of the most important LDAP skills because almost every LDAP integration depends on correct filters.
When LDAP is used for authentication, the client application usually asks LDAP whether a user’s credentials are valid.
User Client Host LDAP Server
| | |
|---Login Request------>| |
| |---Bind/Search-------> |
| | |
| |<--Result------------- |
|<--Access Granted/Denied---------------------- |
Typical steps:
On Debian or Ubuntu, install OpenLDAP and LDAP utilities:
sudo apt-get update
sudo apt-get install slapd ldap-utils
If configuration prompts do not appear, run:
sudo dpkg-reconfigure slapd
Typical configuration values:
For the domain:
example.com
the base DN is:
dc=example,dc=com
Create base.ldif:
dn: dc=example,dc=com
objectClass: top
objectClass: dcObject
objectClass: organization
o: Example Company
dc: example
dn: ou=users,dc=example,dc=com
objectClass: top
objectClass: organizationalUnit
ou: users
dn: ou=groups,dc=example,dc=com
objectClass: top
objectClass: organizationalUnit
ou: groups
Load it:
ldapadd -x -D "cn=admin,dc=example,dc=com" -W -f base.ldif
Expected output:
adding new entry "dc=example,dc=com"
adding new entry "ou=users,dc=example,dc=com"
adding new entry "ou=groups,dc=example,dc=com"
Interpretation:
Use slappasswd:
slappasswd
Example output:
{SSHA}r3wP3fH0QpK8vF5yBEXAMPLEHASH
Use this value in the userPassword attribute.
Avoid storing plain text passwords in LDIF files.
Create jdoe.ldif:
dn: uid=jdoe,ou=users,dc=example,dc=com
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
uid: jdoe
cn: John Doe
sn: Doe
givenName: John
mail: jdoe@example.com
uidNumber: 10000
gidNumber: 10000
homeDirectory: /home/jdoe
loginShell: /bin/bash
userPassword: {SSHA}encrypted_password_here
Add it:
ldapadd -x -D "cn=admin,dc=example,dc=com" -W -f jdoe.ldif
Expected output:
adding new entry "uid=jdoe,ou=users,dc=example,dc=com"
Create developers.ldif:
dn: cn=developers,ou=groups,dc=example,dc=com
objectClass: posixGroup
cn: developers
gidNumber: 10000
memberUid: jdoe
Add it:
ldapadd -x -D "cn=admin,dc=example,dc=com" -W -f developers.ldif
Expected output:
adding new entry "cn=developers,ou=groups,dc=example,dc=com"
Search for a user:
ldapsearch -x -b "ou=users,dc=example,dc=com" "(uid=jdoe)" uid cn mail
Example output:
dn: uid=jdoe,ou=users,dc=example,dc=com
uid: jdoe
cn: John Doe
mail: jdoe@example.com
## numEntries: 1
Search for a group:
ldapsearch -x -b "ou=groups,dc=example,dc=com" "(cn=developers)"
Example output:
dn: cn=developers,ou=groups,dc=example,dc=com
objectClass: posixGroup
cn: developers
gidNumber: 10000
memberUid: jdoe
A Linux client needs a way to use LDAP for identity lookup and authentication.
Common approaches include:
The older packages may still appear in tutorials, but many modern systems prefer SSSD or nslcd-based setups.
The client-side job is to connect Linux account lookup and login authentication to LDAP.
The system components are:
Flow:
getent passwd jdoe
|
v
NSS asks LDAP
|
v
LDAP returns user attributes
|
v
Linux sees jdoe as a valid user
After configuring the client, test user lookup:
getent passwd jdoe
Expected output:
jdoe:x:10000:10000:John Doe:/home/jdoe:/bin/bash
Interpretation:
Check group information:
getent group developers
Expected output:
developers:*:10000:jdoe
Check user identity:
id jdoe
Expected output:
uid=10000(jdoe) gid=10000(developers) groups=10000(developers)
If LDAP users can authenticate but have no home directory, configure PAM to create it automatically.
Install the needed module:
sudo apt-get install libpam-mkhomedir
Add to the PAM session configuration:
session required pam_mkhomedir.so skel=/etc/skel umask=077
Expected behavior:
When jdoe logs in for the first time,
Linux creates /home/jdoe automatically.
Test anonymous access:
ldapwhoami -x -H ldap://localhost
Expected output:
anonymous
Test authenticated bind:
ldapwhoami -x -D "cn=admin,dc=example,dc=com" -W -H ldap://localhost
Expected output:
dn:cn=admin,dc=example,dc=com
Interpretation:
Simple LDAP authentication sends a DN and password to the server.
This should be protected with encryption.
LDAP can be secured in two common ways:
StartTLS begins as a normal LDAP connection and upgrades to TLS.
Example StartTLS test:
ldapwhoami -x -ZZ -D "uid=jdoe,ou=users,dc=example,dc=com" -W -H ldap://ldap.example.com
The -ZZ option requires StartTLS.
If TLS cannot be established, the command fails.
The server needs:
The client needs to trust the CA certificate.
A conceptual TLS flow:
LDAP client
|
| StartTLS request
v
LDAP server presents certificate
|
v
Client verifies certificate
|
v
Encrypted LDAP session begins
If certificate validation fails, the client should refuse the connection.
LDAP can enforce password rules through password policy support.
Common policy rules include:
Example policy ideas:
Password policies help reduce weak passwords and repeated brute-force attempts.
LDAP should be backed up regularly.
A common backup command is:
slapcat > ldap-backup.ldif
This exports the directory database to LDIF.
A restore may use:
slapadd -l ldap-backup.ldif
Typical safe backup workflow:
LDAP replication means keeping multiple LDAP servers synchronized.
A typical layout:
+-------------------+
| Primary LDAP |
| ldap1.example.com |
+---------+---------+
|
replication updates
|
+---------v---------+
| Secondary LDAP |
| ldap2.example.com |
+-------------------+
Replication improves:
If the primary server fails, clients may still authenticate using a replica if configured correctly.
LDAP access control determines who can read or modify entries.
Common rules include:
Access control is important because LDAP contains sensitive identity data.
Poor access control can expose user information or allow unauthorized changes.
Practice identifying a server availability problem.
On a test LDAP server:
sudo systemctl stop slapd
ldapwhoamildapwhoami -x -H ldap://localhost
Example output:
ldap_sasl_bind(SIMPLE): Can't contact LDAP server (-1)
systemctl status slapd
Example output:
● slapd.service - LSB: OpenLDAP standalone server
Active: inactive (dead)
Interpretation:
sudo systemctl start slapd
Verify:
ldapwhoami -x -H ldap://localhost
Expected output:
anonymous
Practice diagnosing connection target mistakes.
Use the wrong port:
ldapsearch -x -H ldap://localhost:1389 -b "dc=example,dc=com" "(objectClass=*)"
Example output:
ldap_sasl_bind(SIMPLE): Can't contact LDAP server (-1)
ss -tulnp | grep slapd
Example output:
tcp LISTEN 0 128 0.0.0.0:389 0.0.0.0:* users:(("slapd",pid=1200,fd=8))
Interpretation:
Use the correct URI:
ldapsearch -x -H ldap://localhost:389 -b "dc=example,dc=com" "(objectClass=*)"
Practice identifying authentication failure.
Run a bind with the wrong password:
ldapwhoami -x -D "cn=admin,dc=example,dc=com" -W -H ldap://localhost
Enter the wrong password.
Example output:
ldap_bind: Invalid credentials (49)
Interpretation:
Try a known working bind:
ldapwhoami -x -D "cn=admin,dc=example,dc=com" -W -H ldap://localhost
Expected output:
dn:cn=admin,dc=example,dc=com
Practice recognizing search base mistakes.
Use the wrong base DN:
ldapsearch -x -H ldap://localhost -b "dc=wrong,dc=com" "(uid=jdoe)"
Example output:
## search result
search: 2
result: 32 No such object
Interpretation:
Search from the correct base:
ldapsearch -x -H ldap://localhost -b "dc=example,dc=com" "(uid=jdoe)"
Expected result:
result: 0 Success
## numEntries: 1
Distinguish between “search succeeded but no entries matched” and “LDAP error.”
Search for a nonexistent user:
ldapsearch -x -H ldap://localhost -b "dc=example,dc=com" "(uid=nosuchuser)"
Example output:
## search result
search: 2
result: 0 Success
## numResponses: 1
## numEntries: 0
Interpretation:
Check known users:
ldapsearch -x -H ldap://localhost -b "ou=users,dc=example,dc=com" "(objectClass=inetOrgPerson)" uid
Show why an LDAP user may exist but not appear as a Linux login user.
Create a user with only inetOrgPerson attributes and no uidNumber, gidNumber, homeDirectory, or loginShell.
Search finds the user:
ldapsearch -x -b "ou=users,dc=example,dc=com" "(uid=jdoe)"
Example output:
dn: uid=jdoe,ou=users,dc=example,dc=com
objectClass: inetOrgPerson
uid: jdoe
cn: John Doe
sn: Doe
mail: jdoe@example.com
But Linux lookup fails:
getent passwd jdoe
Example output:
No output.
Interpretation:
Add the required object classes and attributes:
Then test again:
getent passwd jdoe
Expected output:
jdoe:x:10000:10000:John Doe:/home/jdoe:/bin/bash
Practice diagnosing TLS certificate or StartTLS problems.
Require StartTLS against a server that is not correctly configured for TLS:
ldapwhoami -x -ZZ -H ldap://localhost
Example output:
ldap_start_tls: Connect error (-11)
additional info: TLS error -8172:Peer's certificate issuer has been marked as not trusted
Interpretation:
grep -v '^#' /etc/ldap/ldap.conf
Example:
TLS_CACERT /etc/ssl/certs/ca-certificates.crt
TLS_REQCERT demand
Possible fixes:
Then test again:
ldapwhoami -x -ZZ -H ldap://localhost
Expected output:
anonymous
Practice diagnosing NSS integration problems.
Assume LDAP search works:
ldapsearch -x -b "dc=example,dc=com" "(uid=jdoe)"
but Linux lookup fails:
getent passwd jdoe
Example output:
No output.
grep '^passwd\|^group\|^shadow' /etc/nsswitch.conf
Example broken output:
passwd: files systemd
group: files systemd
shadow: files
Interpretation:
Depending on the client stack, configure NSS to include LDAP or SSSD.
Example concept:
passwd: files systemd ldap
group: files systemd ldap
shadow: files ldap
Then restart the relevant cache/client service:
sudo systemctl restart nscd
or, if using SSSD:
sudo systemctl restart sssd
Test again:
getent passwd jdoe
Show the difference between authentication and authorization.
A user may authenticate successfully but still not be allowed to access a service.
Assume jdoe can bind successfully:
ldapwhoami -x -D "uid=jdoe,ou=users,dc=example,dc=com" -W
Expected output:
dn:uid=jdoe,ou=users,dc=example,dc=com
But the application requires membership in:
cn=admins,ou=groups,dc=example,dc=com
Search group membership:
ldapsearch -x -b "ou=groups,dc=example,dc=com" "(cn=admins)"
Example output:
dn: cn=admins,ou=groups,dc=example,dc=com
objectClass: posixGroup
cn: admins
gidNumber: 10001
memberUid: alice
Interpretation:
Add jdoe to the required group.
Example modify LDIF:
dn: cn=admins,ou=groups,dc=example,dc=com
changetype: modify
add: memberUid
memberUid: jdoe
Apply:
ldapmodify -x -D "cn=admin,dc=example,dc=com" -W -f add_jdoe_to_admins.ldif
Understand how broad LDAP searches can become slow and how to inspect them.
Run a broad search from the top of the directory:
time ldapsearch -x -H ldap://localhost -b "dc=example,dc=com" "(objectClass=*)" > /tmp/all_ldap_entries.txt
Example output:
real 0m4.820s
user 0m0.120s
sys 0m0.040s
time ldapsearch -x -H ldap://localhost -b "ou=users,dc=example,dc=com" "(uid=jdoe)" uid cn
Example output:
real 0m0.080s
user 0m0.020s
sys 0m0.010s
Interpretation:
Example better search:
ldapsearch -x -b "ou=users,dc=example,dc=com" "(uid=jdoe)" uid cn mail
This requests only selected attributes instead of everything.
When LDAP fails, troubleshoot in layers.
systemctl status slapd
Expected healthy state:
Active: active (running)
ss -tulnp | grep slapd
Expected output:
tcp LISTEN 0 128 0.0.0.0:389 0.0.0.0:* users:(("slapd",pid=1200,fd=8))
If using LDAPS:
port 636
ldapwhoami -x -H ldap://localhost
Expected:
anonymous
If this fails, check service status, firewall, port, URI, and network connectivity.
ldapwhoami -x -D "cn=admin,dc=example,dc=com" -W -H ldap://localhost
Expected:
dn:cn=admin,dc=example,dc=com
If this fails with invalid credentials, check the DN and password.
ldapsearch -x -H ldap://localhost -b "dc=example,dc=com" "(objectClass=*)"
If the result is:
No such object
then the base DN may be wrong or missing.
ldapsearch -x -H ldap://localhost -b "ou=users,dc=example,dc=com" "(uid=jdoe)"
If numEntries is 0, the user may not exist or the filter is wrong.
getent passwd jdoe
id jdoe
If LDAP search works but getent fails, check NSS, SSSD, nslcd, or PAM configuration.
Server logs may be available through systemd:
journalctl -u slapd -b
Kernel-level or authentication logs may also help:
journalctl -xe
sudo less /var/log/auth.log
Look for messages about:
Examples:
Invalid credentials (49):
No such object (32):
Insufficient access (50):
Entry already exists (68):
Connectivity:
ldapwhoami -x -H ldap://localhost
ldapwhoami -x -D "cn=admin,dc=example,dc=com" -W -H ldap://localhost
ldapwhoami -x -ZZ -H ldap://localhost
Search:
ldapsearch -x -b "dc=example,dc=com" "(objectClass=*)"
ldapsearch -x -b "ou=users,dc=example,dc=com" "(uid=jdoe)"
ldapsearch -x -b "ou=groups,dc=example,dc=com" "(cn=developers)"
Add, modify, delete:
ldapadd -x -D "cn=admin,dc=example,dc=com" -W -f entry.ldif
ldapmodify -x -D "cn=admin,dc=example,dc=com" -W -f modify.ldif
ldapdelete -x -D "cn=admin,dc=example,dc=com" -W "uid=jdoe,ou=users,dc=example,dc=com"
Linux identity lookup:
getent passwd jdoe
getent group developers
id jdoe
Server checks:
systemctl status slapd
ss -tulnp | grep slapd
journalctl -u slapd -b
Backup:
slapcat > ldap-backup.ldif
dc=example,dc=com.ldapsearch to find users by uid, mail, and objectClass.ldapwhoami to test anonymous and authenticated binds.getent passwd.ldapwhoami -ZZ.