Get Running Now — LDAP Server in 5 Minutes
Demo first, theory later. Run each command below in order — 5 minutes and you’ll have an LDAP server ready to accept connections:
# Install 389 Directory Server
sudo dnf install 389-ds-base -y
# Create instance configuration file
cat > /tmp/ds-setup.inf << 'EOF'
[general]
config_version = 2
full_machine_name = ldap.company.com
[slapd]
instance_name = company
root_dn = cn=Directory Manager
root_password = StrongP@ssword123!
port = 389
secure_port = 636
[backend-userRoot]
suffix = dc=company,dc=com
sample_entries = yes
EOF
sudo dscreate from-file /tmp/ds-setup.inf
# Enable and start the service
sudo systemctl enable --now dirsrv@company
# Verify connection
ldapsearch -x -H ldap://localhost \
-b "dc=company,dc=com" \
-D "cn=Directory Manager" -W \
"(objectClass=*)" dn
Last command returned a list of DNs? Good — LDAP server is running. Next, I’ll walk through each part so you understand what just happened.
389 Directory Server — Why Choose This?
I’ve run all three: OpenLDAP on Debian, FreeIPA on RHEL 7, and AD integrated with Linux via SSSD. When I need a pure LDAP solution on RHEL/CentOS, 389 DS is the one I keep coming back to — not out of habit, but because it genuinely fits better within the Red Hat ecosystem.
When CentOS 8 reached EOL in 2021, I had to migrate 5 servers to Rocky Linux within a week. The result: servers running 389 DS transitioned with nearly zero reconfiguration — packages were fully compatible, data directories stayed intact, reinstall and point to the right location and you’re done. The servers running OpenLDAP? Half a day each to rebuild the schema.
Quick comparison:
- vs OpenLDAP: 389 DS has a web console (cockpit-389-ds), built-in replication that’s easier to configure, and more detailed logging
- vs FreeIPA: 389 DS is significantly lighter, without pulling in Kerberos/DNS/CA — ideal for RAM-constrained servers or when you only need plain LDAP
- vs Active Directory: No Windows license required, native integration with the Linux ecosystem
Preparing the Environment Before Installation
Configuring Hostname and Firewall
389 DS requires the hostname to be resolvable — this is the most commonly skipped step, and without it dscreate fails with a rather confusing TLS error:
# Set the correct FQDN hostname
sudo hostnamectl set-hostname ldap.company.com
# Add to /etc/hosts if no internal DNS is set up
echo "192.168.1.10 ldap.company.com ldap" | sudo tee -a /etc/hosts
# Open LDAP and LDAPS ports
sudo firewall-cmd --permanent --add-service=ldap
sudo firewall-cmd --permanent --add-service=ldaps
sudo firewall-cmd --reload
# Verify hostname resolves correctly
python3 -c "import socket; print(socket.getfqdn())"
Managing Users and Groups
Creating the OU Structure
The instance is ready. First, create OUs to organize users and groups — this is the most commonly used minimal structure:
cat > /tmp/add-ou.ldif << 'EOF'
dn: ou=users,dc=company,dc=com
objectClass: organizationalUnit
ou: users
dn: ou=groups,dc=company,dc=com
objectClass: organizationalUnit
ou: groups
EOF
ldapadd -x -H ldap://localhost \
-D "cn=Directory Manager" -W \
-f /tmp/add-ou.ldif
Adding a New User
# Generate password hash first
pwdhash -s SSHA "UserPassword123!"
# Example output: {SSHA}abc123...== — copy that string into the userPassword field below
# Create user entry
cat > /tmp/add-user.ldif << 'EOF'
dn: uid=john,ou=users,dc=company,dc=com
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
uid: john
cn: John Doe
sn: Doe
mail: [email protected]
uidNumber: 10001
gidNumber: 10001
homeDirectory: /home/john
loginShell: /bin/bash
userPassword: {SSHA}REPLACE_WITH_PWDHASH_OUTPUT
EOF
ldapadd -x -H ldap://localhost \
-D "cn=Directory Manager" -W \
-f /tmp/add-user.ldif
Searching and Verifying
# Find the newly created user
ldapsearch -x -H ldap://localhost \
-D "cn=Directory Manager" -W \
-b "ou=users,dc=company,dc=com" \
"(uid=john)" uid cn mail uidNumber
# Test login using that account
ldapwhoami -x -H ldap://localhost \
-D "uid=john,ou=users,dc=company,dc=com" \
-W
Advanced — Enabling TLS and Integrating Linux Clients
Enabling LDAPS (Required for Production)
Running LDAP without TLS in production means credentials travel in plaintext across the network — don’t do that. 389 DS provides the dsconf tool to manage certificates:
# If using Let's Encrypt or an internal CA
sudo dsconf company security certificate add \
--file /etc/pki/tls/certs/ldap.crt \
--name "server-cert"
sudo dsconf company security rsa enable
# Or generate a self-signed cert with dsctl
sudo dsctl company tls generate-server-cert-csr \
--subject "CN=ldap.company.com,O=Company,C=VN"
sudo systemctl restart dirsrv@company
# Test LDAPS
ldapsearch -x -H ldaps://ldap.company.com \
-D "cn=Directory Manager" -W \
-b "dc=company,dc=com" "(uid=john)"
Integrating Linux Clients with sssd
Want other servers to authenticate against this LDAP? Install sssd on each client and point it to the LDAP server you just set up:
# On the client machine
sudo dnf install sssd sssd-ldap authselect -y
sudo authselect select sssd with-mkhomedir --force
sudo tee /etc/sssd/sssd.conf << 'EOF'
[sssd]
services = nss, pam
config_file_version = 2
domains = company.com
[domain/company.com]
id_provider = ldap
auth_provider = ldap
ldap_uri = ldaps://ldap.company.com
ldap_search_base = dc=company,dc=com
ldap_default_bind_dn = cn=Directory Manager
ldap_default_authtok_type = password
ldap_default_authtok = StrongP@ssword123!
ldap_tls_cacert = /etc/pki/ca-trust/source/anchors/company-ca.crt
ldap_id_use_start_tls = True
cache_credentials = True
EOF
sudo chmod 600 /etc/sssd/sssd.conf
sudo systemctl enable --now sssd
# Verify the LDAP user is visible
id john
Configuring Password Policy
# Enable 389 DS built-in password policy
sudo dsconf company pwpolicy set \
--pwdminage 0 \
--pwdmaxage 90 \
--pwdminlength 8 \
--pwdlockout on \
--pwdmaxfailure 5
Practical Tips from Real-World Operations
- Automated daily backups: Add a cronjob running
sudo dsctl company db2bak /backup/ldap-$(date +%Y%m%d)— saves both schema and data. I keep 7 days’ worth and clean older ones withfind /backup -name 'ldap-*' -mtime +7 -delete. - Web console: Install
cockpit-389-dsand openhttps://ldap.company.com:9090— I use this regularly when I need to inspect schemas or debug replication status, much faster than typing ldapsearch commands manually. - SELinux doesn’t need to be disabled: 389 DS works fine with SELinux enforcing on CentOS Stream 9. Don’t trust those 2015 guides telling you to run
setenforce 0— it’s no longer necessary. - Log rotation: A system with 500+ active users can generate several gigabytes of access logs per month. Limit the size with:
sudo dsconf company config replace nsslapd-accesslog-maxlogsize=100 - Monitor connections: Check current connections with
sudo dsconf company monitor server | grep -i connection— normally around 10–50 concurrent connections. A sudden spike to several hundred is a sign of a client misconfiguration causing a reconnect loop.
Quick Troubleshooting
Connection Errors
# Check which ports are listening
sudo ss -tlnp | grep -E ':389|:636'
# View error logs in real time
sudo journalctl -u dirsrv@company -f
# Or check the detailed access log
sudo tail -f /var/log/dirsrv/slapd-company/access
Authentication Errors
# Manually test bind
ldapwhoami -x -H ldap://localhost \
-D "uid=john,ou=users,dc=company,dc=com" \
-W
# Check that the password hash is in the correct format
ldapsearch -x -H ldap://localhost \
-D "cn=Directory Manager" -W \
-b "uid=john,ou=users,dc=company,dc=com" \
userPassword
That’s everything you need to run a centralized LDAP server for your Linux infrastructure. No Windows, no license fees, and when you eventually upgrade to RHEL 10, packages remain fully compatible. Need high availability? The next step is multi-master replication — 389 DS supports 2–4 master nodes, and it’s significantly simpler to configure than it was a few years ago.
