OpenLDAP Deployment Guide: cn=config, TLS, ACLs, Backups, and Replication
EdwardMoon
This OpenLDAP deployment guide covers TLS, least-privilege ACLs, indexes, backup and restore, and syncrepl high availability using OpenLDAP 2.6's dynamic cn=config configuration and MDB backend. Because directory services underpin authentication, verify encryption, permissions, consistency, and recoverability before declaring the installation successful.
On Rocky Linux 9, server package sources and directory layouts vary by vendor and build method. This guide assumes a verified OpenLDAP 2.6 package installation. Inspect package-specific paths first, then modify cn=config through LDAP operations. For host login integration, consider SSSD's LDAP provider instead of nss-pam-ldapd.

Components and Design
| Component | Role | Operational requirements |
|---|---|---|
| slapd | Handles LDAP requests | Unprivileged service account, systemd, and TLS listeners |
| cn=config | Live dynamic configuration DIT | Modify through LDAPI and SASL EXTERNAL |
| MDB | Stores directory data | Map size, checkpoints, and filesystem capacity |
| ACL | Permissions by attribute and DN | Specific rules first; deny by default |
| TLS | Encrypts binds, searches, and replication | CA trust, hostname verification, and expiration management |
| syncrepl | Synchronizes changes between providers | Unique serverIDs and rids, with synchronized clocks |
| slapcat/slapadd | Logical backup and restore | Preserve configuration and data together |
Check Packages and Paths
Do not guess package names. Record the binary, configuration DIT, data directory, and certificate paths used by the service. For an OpenLDAP Project source build, retain configure, test, and install results alongside dependency versions. For vendor packages, verify GPG signatures and the support policy.
slapd -VVV 2>&1 | head -n 40
ldapsearch -VV 2>&1 | head -n 20
command -v slapd ldapsearch ldapmodify slapcat slapadd slaptest
systemctl cat slapd
systemctl show slapd -p User -p Group -p ExecStart
findmnt -T /var/lib/ldap
df -hT /var/lib/ldap
df -i /var/lib/ldap
Do not add arbitrary internet repositories or pipe unverified installation scripts into a shell. Patch the OpenLDAP 2.6 maintenance release, TLS library, LMDB, Cyrus SASL, and systemd configuration together, and check LDIF compatibility in staging.
Inspect cn=config and MDB
Connect Locally as an Administrator over LDAPI
sudo ldapwhoami -Q -Y EXTERNAL -H ldapi:///
sudo ldapsearch -LLL -Q -Y EXTERNAL -H ldapi:/// -b cn=config -s base dn cn
sudo ldapsearch -LLL -Q -Y EXTERNAL -H ldapi:/// -b cn=config '(&(objectClass=olcMdbConfig)(olcSuffix=*))' dn olcSuffix olcRootDN olcDbDirectory olcDbMaxSize olcDbIndex
-Q -Y EXTERNAL uses Unix socket peer credentials, avoiding a cn=config password on the command line. Keep cn=config off remote interfaces, and back up current entries as LDIF before changing them. The {number} in a database DN varies by installation; discover it rather than assuming a fixed value.
Back Up the Current cn=config
sudo install -d -m 0700 /var/backups/openldap
sudo ldapsearch -LLL -Q -Y EXTERNAL -H ldapi:/// -b cn=config '(objectClass=*)' '*' '+' | sudo tee "/var/backups/openldap/cn-config-before-$(date +%F-%H%M%S).ldif" >/dev/null
sudo chmod 0600 /var/backups/openldap/*.ldif
sudo sha256sum /var/backups/openldap/*.ldif
Set the Suffix and Administrator DN
The example uses dc=example,dc=com and cn=admin. First establish the organization's actual name, DNS ownership, and naming policy. rootDN bypasses all ACLs and is not an application account. Reserve it for emergency recovery and delegate routine changes to an administrator group.
Find the MDB Database DN and Generate a rootPW Hash
MDB_DN=$(sudo ldapsearch -LLL -Q -Y EXTERNAL -H ldapi:/// -b cn=config '(&(objectClass=olcMdbConfig)(olcSuffix=*))' dn | awk '/^dn: / {print substr($0,5); exit}')
printf 'MDB_DN=%s
' "$MDB_DN"
test -n "$MDB_DN"
# Enter the password interactively so it is not stored in shell history.
ROOT_HASH=$(slappasswd)
test -n "$ROOT_HASH"
Change suffix, rootDN, and rootPW
sudo install -m 0600 /dev/null /root/openldap-database.ldif
sudo tee /root/openldap-database.ldif >/dev/null <<EOF
dn: ${MDB_DN}
changetype: modify
replace: olcSuffix
olcSuffix: dc=example,dc=com
-
replace: olcRootDN
olcRootDN: cn=admin,dc=example,dc=com
-
replace: olcRootPW
olcRootPW: ${ROOT_HASH}
EOF
unset ROOT_HASH
sudo ldapmodify -Q -Y EXTERNAL -H ldapi:/// -f /root/openldap-database.ldif
sudo shred -u /root/openldap-database.ldif
Create the Base DIT and Users
Create the Base DN and Organizational Units
install -m 0600 /dev/null base.ldif
cat > base.ldif <<'LDIF'
dn: dc=example,dc=com
objectClass: top
objectClass: dcObject
objectClass: organization
o: Example Directory
dc: example
dn: ou=People,dc=example,dc=com
objectClass: organizationalUnit
ou: People
dn: ou=Groups,dc=example,dc=com
objectClass: organizationalUnit
ou: Groups
LDIF
ldapadd -x -W -D 'cn=admin,dc=example,dc=com' -H ldapi:/// -f base.ldif
shred -u base.ldif
Create a Test User and Administrator Group
USER_HASH=$(slappasswd)
install -m 0600 /dev/null people.ldif
cat > people.ldif <<EOF
dn: uid=alice,ou=People,dc=example,dc=com
objectClass: inetOrgPerson
uid: alice
cn: Alice Example
sn: Example
mail: alice@example.com
userPassword: ${USER_HASH}
dn: cn=ldap-admins,ou=Groups,dc=example,dc=com
objectClass: groupOfNames
cn: ldap-admins
member: uid=alice,ou=People,dc=example,dc=com
EOF
unset USER_HASH
ldapadd -x -W -D 'cn=admin,dc=example,dc=com' -H ldapi:/// -f people.ldif
shred -u people.ldif
Do not store plaintext user passwords in LDIF, Git, or tickets. Separate application service accounts from personal accounts, and consider schema requirements when removing the first member of groupOfNames. Enforce uid and mail uniqueness through an overlay or provisioning workflow rather than relying solely on applications.
Require and Validate TLS
olcSecurity: simple_bind=128 requires sufficient encryption for password-based simple binds. If olcSecurity already exists, preserve and merge its restrictions instead of blindly overwriting it. LDAPI simple binds may also be rejected when local SSF is below 128. Use LDAPI SASL EXTERNAL for subsequent administrative changes, and CA-validated StartTLS/LDAPS for user and replication accounts. This server policy does not itself encrypt a password sent by a client; clients must explicitly use -ZZ or ldaps://.
A simple bind without TLS does not protect the password. Include the DNS names clients actually use in the certificate SANs, and allow only the slapd service account to read the key. Distribute the organizational or public CA chain to all LDAP clients and replication nodes.
Check Certificate Permissions and Validity
sudo install -d -o root -g ldap -m 0750 /etc/openldap/certs
sudo install -o root -g ldap -m 0640 ldap01.key /etc/openldap/certs/
sudo install -o root -g ldap -m 0644 ldap01.crt /etc/openldap/certs/
sudo install -o root -g ldap -m 0644 org-ca.crt /etc/openldap/certs/
openssl x509 -in /etc/openldap/certs/ldap01.crt -noout -subject -issuer -dates -ext subjectAltName
openssl verify -CAfile /etc/openldap/certs/org-ca.crt /etc/openldap/certs/ldap01.crt
Apply TLS Paths in cn=config
sudo ldapmodify -Q -Y EXTERNAL -H ldapi:/// <<'LDIF'
dn: cn=config
changetype: modify
replace: olcTLSCACertificateFile
olcTLSCACertificateFile: /etc/openldap/certs/org-ca.crt
-
replace: olcTLSCertificateFile
olcTLSCertificateFile: /etc/openldap/certs/ldap01.crt
-
replace: olcTLSCertificateKeyFile
olcTLSCertificateKeyFile: /etc/openldap/certs/ldap01.key
-
replace: olcTLSProtocolMin
olcTLSProtocolMin: 3.3
-
replace: olcSecurity
olcSecurity: simple_bind=128
LDIF
sudo systemctl restart slapd
sudo systemctl status slapd --no-pager
Verify Hostnames over StartTLS and LDAPS
openssl s_client -connect ldap01.example.com:636 -servername ldap01.example.com -verify_hostname ldap01.example.com -CAfile /etc/openldap/certs/org-ca.crt -verify_return_error </dev/null
LDAPTLS_CACERT=/etc/openldap/certs/org-ca.crt ldapwhoami -x -ZZ -H ldap://ldap01.example.com -D 'uid=alice,ou=People,dc=example,dc=com' -W
LDAPTLS_CACERT=/etc/openldap/certs/org-ca.crt ldapsearch -LLL -x -H ldaps://ldap01.example.com -D 'uid=alice,ou=People,dc=example,dc=com' -W -b 'dc=example,dc=com' '(uid=alice)' dn cn mail
Create a Dedicated Replication Account and Test Unencrypted Bind Rejection
Prepare the dedicated account used by both providers first. Its password must match the secret in the replication connection configuration. If the account already exists, verify it and its ACLs instead of creating another entry.
REPL_HASH=$(slappasswd)
umask 077
cat > replicator.ldif <<EOF
dn: uid=replicator,ou=People,dc=example,dc=com
objectClass: inetOrgPerson
uid: replicator
cn: Replication Reader
sn: Reader
userPassword: ${REPL_HASH}
EOF
unset REPL_HASH
LDAPTLS_CACERT=/etc/openldap/certs/org-ca.crt \
ldapadd -x -ZZ -H ldap://ldap01.example.com \
-D 'cn=admin,dc=example,dc=com' -W -f replicator.ldif
rm -f replicator.ldif
# Send only a test string, not a real password. Verify the confidentiality required rejection.
ldapwhoami -x -H ldap://ldap01.example.com \
-D 'uid=transport-probe,ou=People,dc=example,dc=com' -w not-a-real-password
Configure Least-Privilege ACLs
OpenLDAP evaluates ACLs from top to bottom according to the first matching to and by rules. Handle sensitive attributes such as userPassword first, and place general rules last. Do not rely on public read access; permit anonymous auth access only where needed for binds.
sudo tee /root/openldap-acl.ldif >/dev/null <<EOF
dn: ${MDB_DN}
changetype: modify
replace: olcAccess
olcAccess: {0}to * by dn.exact="uid=replicator,ou=People,dc=example,dc=com" read by * break
olcAccess: {1}to attrs=userPassword by self =xw by anonymous auth by * none
olcAccess: {2}to dn.subtree="ou=People,dc=example,dc=com" attrs=entry,children,cn,sn,uid,mail by self write by group.exact="cn=ldap-admins,ou=Groups,dc=example,dc=com" write by users read by * none
olcAccess: {3}to dn.subtree="ou=Groups,dc=example,dc=com" by group.exact="cn=ldap-admins,ou=Groups,dc=example,dc=com" write by users read by * none
olcAccess: {4}to * by users read by * none
-
add: olcLimits
olcLimits: {0}dn.exact="uid=replicator,ou=People,dc=example,dc=com" time.soft=unlimited time.hard=unlimited size.soft=unlimited size.hard=unlimited
EOF
sudo chmod 0600 /root/openldap-acl.ldif
sudo ldapmodify -Q -Y EXTERNAL -H ldapi:/// -f /root/openldap-acl.ldif
sudo shred -u /root/openldap-acl.ldif
Test Allowed and Denied Access
# Anonymous clients must not be able to read userPassword.
LDAPTLS_CACERT=/etc/openldap/certs/org-ca.crt ldapsearch -LLL -x -ZZ -H ldap://ldap01.example.com -b 'uid=alice,ou=People,dc=example,dc=com' userPassword
# Verify the user's own bind and access to ordinary attributes.
LDAPTLS_CACERT=/etc/openldap/certs/org-ca.crt ldapsearch -LLL -x -ZZ -H ldap://ldap01.example.com -D 'uid=alice,ou=People,dc=example,dc=com' -W -b 'uid=alice,ou=People,dc=example,dc=com' cn mail
sudo ldapsearch -LLL -Q -Y EXTERNAL -H ldapi:/// -b "$MDB_DN" olcAccess
MDB Indexes and Health
Base indexes on actual search filters and bind attributes. Adding eq and sub indexes to every attribute increases write costs and database size. Analyze access logs or application queries first. If data already exists, run slapindex in a maintenance window.
sudo tee /root/openldap-index.ldif >/dev/null <<EOF
dn: ${MDB_DN}
changetype: modify
replace: olcDbIndex
olcDbIndex: objectClass eq
olcDbIndex: entryUUID,entryCSN eq
olcDbIndex: uid,mail eq
olcDbIndex: cn,sn eq,sub
EOF
sudo ldapmodify -Q -Y EXTERNAL -H ldapi:/// -f /root/openldap-index.ldif
sudo shred -u /root/openldap-index.ldif
sudo ldapsearch -LLL -Q -Y EXTERNAL -H ldapi:/// -b "$MDB_DN" olcDbIndex olcDbMaxSize olcDbCheckpoint
Indexing existing data and restoring data-directory ownership depend on package paths. Remove slapd from service and stop it before running slapindex, then check slaptest and ownership before restarting. Do not arbitrarily copy or modify live production MDB files.
slapcat Backups and Restore Drills
Back up both cn=config and the application suffix. For a single node, stop slapd during a maintenance window. In HA, remove one replica from the load balancer and create a consistent LDIF backup on that node. Encrypt the files, copy them to a separate system, and record checksums.
sudo bash -euo pipefail <<'BASH'
backup_dir="/var/backups/openldap/$(date +%F-%H%M%S)"
install -d -m 0700 "$backup_dir"
umask 077
systemctl stop slapd
trap 'systemctl start slapd' EXIT
slapcat -F /etc/openldap/slapd.d -n 0 > "$backup_dir/cn-config.ldif"
slapcat -F /etc/openldap/slapd.d -b 'dc=example,dc=com' > "$backup_dir/example-data.ldif"
test -s "$backup_dir/cn-config.ldif"
test -s "$backup_dir/example-data.ldif"
(cd "$backup_dir" && sha256sum ./*.ldif > SHA256SUMS && sha256sum -c SHA256SUMS)
BASH
sudo systemctl status slapd --no-pager
sudo journalctl -u slapd --since '-5 min' --no-pager
Restore Drill Checklist
sudo slaptest -F /etc/openldap/slapd.d -u
sudo slapcat -F /etc/openldap/slapd.d -n 0 | head
sudo slapcat -F /etc/openldap/slapd.d -b 'dc=example,dc=com' | head
LDAPTLS_CACERT=/etc/openldap/certs/org-ca.crt ldapsearch -LLL -x -ZZ -H ldap://ldap01.example.com -D 'cn=admin,dc=example,dc=com' -W -b 'dc=example,dc=com' '(objectClass=*)' dn | tail
- On an isolated host with empty slapd.d and data directories, run slapadd for configuration first, then data.
- Restore ownership to the package's service account and restore SELinux contexts.
- Run slaptest, then validate LDAPI, StartTLS/LDAPS, and hostname verification.
- Sample-check the base DN, entry count, important group memberships, and password binds.
- Retest permitted application-account operations and anonymous access denial.
- Record recovery time and missing data to refine RPO and RTO.
syncrepl and High Availability
The replication account must read all source data, including userPassword, and operational attributes to maintain identical authentication data. The first ACL above grants full read access only to the replication DN; other connections continue through the existing rules via by * break. Do not reuse this account in applications. If the package provides syncprov as a dynamic module, identify its supplied module path and load it first.
A multiprovider configuration does not mean every client should write to both providers simultaneously. To reduce conflicts and split-brain risk, have the load balancer select one active write path at a time, and design read distribution, health checks, and failback separately. Assign unique serverIDs and syncrepl rids.
Configure the syncprov Overlay and Checkpoints
sudo ldapadd -Q -Y EXTERNAL -H ldapi:/// <<EOF
dn: olcOverlay=syncprov,${MDB_DN}
objectClass: olcOverlayConfig
objectClass: olcSyncProvConfig
olcOverlay: syncprov
olcSpCheckpoint: 100 10
olcSpSessionlog: 1000
EOF
Example syncrepl Structure for Each Provider
dn: cn=config
changetype: modify
replace: olcServerID
olcServerID: 1 ldaps://ldap01.example.com
olcServerID: 2 ldaps://ldap02.example.com
dn: olcDatabase={MDB_NUMBER}mdb,cn=config
changetype: modify
add: olcSyncrepl
olcSyncrepl: rid=001 provider=ldaps://ldap02.example.com bindmethod=simple binddn="uid=replicator,ou=People,dc=example,dc=com" credentials=<REPLICATION_SECRET> searchbase="dc=example,dc=com" type=refreshAndPersist retry="5 5 300 +" timeout=10 tls_reqcert=demand tls_cacert=/etc/openldap/certs/org-ca.crt
-
add: olcMultiProvider
olcMultiProvider: TRUE
This LDIF illustrates the structure. Adapt {MDB_NUMBER}, serverID, rid, provider, and the secret for each node, and apply it from a protected file. Simple-bind secrets are stored in cn=config, so strengthen directory permissions and backup encryption, or consider SASL EXTERNAL with client certificates.
Verify Replication Consistency and Failover
LDAPTLS_CACERT=/etc/openldap/certs/org-ca.crt ldapsearch -LLL -x -H ldaps://ldap01.example.com -D 'uid=replicator,ou=People,dc=example,dc=com' -W -b 'dc=example,dc=com' -s base contextCSN
LDAPTLS_CACERT=/etc/openldap/certs/org-ca.crt ldapsearch -LLL -x -H ldaps://ldap02.example.com -D 'uid=replicator,ou=People,dc=example,dc=com' -W -b 'dc=example,dc=com' -s base contextCSN
sudo ldapsearch -LLL -Q -Y EXTERNAL -H ldapi:/// -b cn=Monitor '(objectClass=monitorOperation)' cn monitorOpInitiated monitorOpCompleted
timedatectl status
chronyc tracking
Confirm that adding, modifying, and deleting a test entry reaches the other provider. Then remove one provider from the load balancer and test reads, writes, and rejoining. Different contextCSN values alone do not prove a failure; also inspect logs, pending operations, connection state, and convergence time.
Operational Checks
systemctl --no-pager --full status slapd
ss -lntp | grep -E ':(389|636)\b'
sudo journalctl -u slapd --since '-30 min' --no-pager
sudo ldapsearch -LLL -Q -Y EXTERNAL -H ldapi:/// -b cn=Monitor -s base '*' '+'
df -hT /var/lib/ldap /var/log
df -i /var/lib/ldap /var/log
findmnt -T /var/lib/ldap
- Inspect the systemd exit code and first error timestamp.
- Verify that ports 389 and 636 listen only on intended addresses.
- Check the CA chain, SANs, validity period, and client hostname verification.
- Reproduce bind DN, base DN, filter, scope, and ACL order with a minimal query.
- Check MDB map size, filesystem and inode capacity, checkpoints, and long-running queries.
- Inspect syncrepl connections, serverIDs, rids, contextCSNs, and time synchronization.
- Check the latest slapcat checksums and isolated restore drill results.
Common Mistakes and Alternatives
| Mistake | Impact | Alternative |
|---|---|---|
| Plaintext simple binds | Exposes passwords | Require StartTLS or LDAPS with CA verification |
| Using rootDN in applications | Bypasses all ACLs | Use service DNs and least-privilege groups |
| Full anonymous read access | Exposes personal information and organizational structure | Allow only password authentication access, then deny by default |
| Assuming fixed database DN numbers | May modify the wrong entry on another installation | Discover the actual DN with a cn=config search |
| Copying live MDB files | Inconsistent backup | Use slapcat and test restoration |
| Unrestricted concurrent writes to both providers | Conflicts and split brain | Use one active write path with controlled failover |
| Skipping TLS verification | May connect to an impersonating server | Validate the CA, SANs, hostname, and expiration |
Related Resources
- Official OpenLDAP 2.6 Administrator's Guide
- OpenLDAP Access Control guide
- OpenLDAP Replication guide
- Red Hat SSSD LDAP and TLS client guide
- Linux Disk Space and Inode Troubleshooting
- DRBD, Pacemaker, and STONITH High Availability
- Ansible Playbooks, Vault, and Rolling Deployments
Conclusion
One successful bind does not complete an OpenLDAP deployment. Verify cn=config and MDB paths, TLS hostname checks, attribute-level ACLs, query-driven indexes, paired configuration/data backups, and isolated restoration. For predictable authentication services under HA, maintain unique serverIDs and rids, synchronized clocks, and a single active write path.