Fullmoon System

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.

OpenLDAP architecture: TLS clients, load balancer, two-node syncrepl, and encrypted backups
An OpenLDAP deployment separating TLS clients, one active write path, syncrepl between two providers, and off-cluster backups

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
LDAP is not a general-purpose relational database. It suits read-heavy identity and directory data with stable hierarchies and schemas. An RDBMS is better suited to transactional business data, arbitrary ad hoc relationships, or frequently changing schemas.

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
  1. Inspect the systemd exit code and first error timestamp.
  2. Verify that ports 389 and 636 listen only on intended addresses.
  3. Check the CA chain, SANs, validity period, and client hostname verification.
  4. Reproduce bind DN, base DN, filter, scope, and ACL order with a minimal query.
  5. Check MDB map size, filesystem and inode capacity, checkpoints, and long-running queries.
  6. Inspect syncrepl connections, serverIDs, rids, contextCSNs, and time synchronization.
  7. 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

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.