This guide explains how to back up and restore a complete OpenLDAP deployment using slapcat and slapadd on the RHEL family.
You will learn how to:
- Back up the
cn=configconfiguration database - Discover every configured MDB database
- Back up each MDB database once using a suffix selector
- Include replication accesslog databases when they exist
- Preserve database and package inventory
- Compress and checksum backup files
- Automate backups with a systemd timer
- Remove expired backups safely
- Restore one directory database
- Perform a complete disaster-recovery restore
- Validate configuration, data, TLS, ACLs, and authentication
- Test recovery on an isolated host
The procedure applies to OpenLDAP 2.6 deployments across Red Hat Enterprise Linux, Rocky Linux, AlmaLinux, Oracle Linux, and CentOS Stream.
Tested on: Rocky Linux 10.2 with
openldap-servers2.6.10-1.el10_2 from EPEL 10.2.
| Lab setting | Value |
|---|---|
| OpenLDAP server | ldap-server.example.com |
| Recovery-test host | ldap-client.example.com |
| Configuration database | cn=config |
| Main directory suffix | dc=example,dc=com |
| Main database directory | /var/lib/ldap |
| Optional replication log | cn=accesslog (when configured) |
| Configuration directory | /etc/openldap/slapd.d |
| Backup directory | /var/backups/openldap |
| OpenLDAP service | slapd |
Although ldap-client is normally an LDAP authentication client in this lab, it temporarily runs openldap-servers as an isolated recovery-test host later in the article.
Complete these guides first:
- Install and configure OpenLDAP on the RHEL family
- OpenLDAP cn=config and MDB explained
- Configure OpenLDAP TLS with StartTLS
slapcat confirms that LDIF files were created; it does not prove that the files, configuration, certificates, and recovery procedure can rebuild a working server.
| Your goal | Go to |
|---|---|
| Create an immediate backup | Create and Run the OpenLDAP Backup |
| Schedule daily backups | Automate, Retain and Monitor Backups |
| Restore one damaged database | Restore One MDB Database |
| Rebuild the complete server | Perform Complete Disaster Recovery |
| Test whether backups work | Run an Isolated Recovery Drill |
Plan What to Back Up
Before you write a script, decide what “recovered” means for your directory.
| Term | Meaning | Example |
|---|---|---|
| Recovery Point Objective (RPO) | Maximum acceptable amount of lost changes | Up to 24 hours for a daily backup |
| Recovery Time Objective (RTO) | Maximum acceptable recovery duration | Restore service within two hours |
Your backup schedule should follow directory write frequency, password-change patterns, application provisioning activity, replication topology, acceptable data-loss window, restore duration, and available backup storage.
For a one-off export before editing cn=config, see the manual backup steps in OpenLDAP cn=config and MDB explained. This article owns recurring backup, retention, restoration, and disaster-recovery testing.
What a Complete Backup Includes
| Component | Included by slapcat? |
Separate backup required? |
|---|---|---|
cn=config |
Yes, with slapcat -n 0 |
No |
| Loaded schema definitions | Yes, through cn=config |
Keep source files when custom |
| ACLs and database indexes | Yes, through cn=config |
No |
| Main MDB directory data | Yes, with slapcat -g -b |
No |
| Additional MDB databases | Yes, one dump per olcMdbConfig entry |
No |
cn=accesslog data |
Yes, when separately dumped | No |
| TLS CA and server certificates | Paths only | Yes |
| TLS private key | Path only | Yes |
/etc/sysconfig/slapd |
No | Yes |
| systemd overrides | No | Yes |
| Custom module binaries | No | Yes or reinstall package |
| Firewall configuration | No | Document or back up separately |
| SELinux custom rules | No | Document or export separately |
| Monitoring and backup scripts | No | Yes |
The cn=config export describes databases, schemas, modules, ACLs, and overlays. It does not contain the entries stored inside each MDB database. Each physical MDB database needs one slapcat -g -b export, even when that database serves multiple suffix values. Certificate paths inside cn=config do not contain the certificate files themselves. Verify the live values of olcTLSCACertificateFile, olcTLSCertificateFile, and olcTLSCertificateKeyFile on your host—the lab uses /etc/openldap/certs/, but your paths may differ.
Back up, where present:
/etc/openldap/certs/ (lab path — verify olcTLS* paths on your host)
/etc/sysconfig/slapd
/etc/systemd/system/slapd.service.d/
/usr/local/sbin/openldap-backup
/etc/systemd/system/openldap-backup.service
/etc/systemd/system/openldap-backup.timer
custom schema source files
custom module files
overlay external files referenced outside cn=configThe backup script builds system-files.tar.gz only from paths that exist, so a missing optional file does not fail the job. Custom modules and overlay dependencies that live outside these paths must be recorded in the manifest or archived separately. Certificate creation and renewal belong in Configure OpenLDAP TLS with StartTLS.
Why This Guide Uses slapcat
| Method | Output | Online use | Primary purpose |
|---|---|---|---|
slapcat |
Portable LDIF with operational attributes | Supported for MDB | Logical backup and recovery |
slapadd |
Imports LDIF into an offline database | No | Restore |
ldapsearch |
LDAP search output | Yes | Querying and reporting |
mdb_copy |
Native LMDB environment copy | Yes | Fast binary MDB backup |
cp or tar of live MDB files |
Raw files | Unsafe as a general live method | Avoid for normal backup |
This guide uses slapcat and slapadd because:
slapcatpreserves stored operational attributes such asentryUUIDandentryCSN- Its output is intended for
slapadd - LDIF is portable across compatible systems
- Suffix-based commands with
-gavoid assuming database numbers and prevent glued subordinate duplication - Logical restores rebuild indexes according to the restored configuration
OpenLDAP also supports live LMDB copies with mdb_copy. That method can be faster for very large databases, but native MDB copies have stricter compatibility requirements. Keep mdb_copy as a secondary advanced strategy.
Do not use ldapsearch as the complete backup method, and do not strip operational attributes from a slapcat backup intended for slapadd.
Choose Online or Quiesced Backup
Online backup
slapcat can export an MDB database while slapd is running. Use online backups when brief application-level differences are acceptable, directory updates are independent, the RPO is more important than exact transaction grouping, or the backup runs frequently.
Quiesced backup
Use a write freeze when one business transaction contains multiple LDAP operations, several suffixes must represent the same logical point in time, provisioning jobs modify users and groups together, or a recovery point must be application-consistent.
Possible approaches:
- Pause provisioning applications.
- Temporarily block writes at the application layer.
- Temporarily make affected databases read-only.
- Stop
slapdfor a fully offline backup.
Do not automatically toggle olcReadOnly inside the daily script without considering replication, password changes, service-account updates, application failures, and multiple MDB databases. The read-only workflow is documented in OpenLDAP cn=config and MDB explained.
A practical starting schedule:
Daily: online logical backup
Weekly or before major changes: quiesced backup
Quarterly: full restore drillAdjust the schedule to your required RPO and RTO.
cn=accesslog database, the main MDB database and accesslog form one replication state. Use a quiesced or offline backup when you need a restorable delta-syncrepl provider. Sequential online slapcat exports are individually valid, but they do not guarantee that the main database and accesslog represent exactly the same recovery point.
Protect Backup Secrets
Backup files can contain password hashes, root-password hashes, service-account secrets, and replication credentials. Treat every backup directory as confidential.
Discover MDB Databases and Backup Paths
Back up by suffix rather than assuming fixed database numbers such as -n 1 or -n 2. List every configured database first.
sudo ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b cn=config '(objectClass=olcDatabaseConfig)' dn olcDatabase olcSuffixThat query shows every database entry under cn=config, including the config, monitor, and frontend databases.
To list MDB data databases only, filter on olcMdbConfig:
sudo ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b cn=config '(objectClass=olcMdbConfig)' dn olcSuffix olcDbDirectory olcDbMaxSizeSample output:
dn: olcDatabase={2}mdb,cn=config
olcDbDirectory: /var/lib/ldap
olcSuffix: dc=example,dc=comOn a replication provider you may also see cn=accesslog with its own olcDbDirectory, for example /var/lib/ldap-accesslog. That custom path needs a persistent slapd_db_t SELinux mapping during recovery, as shown later in the full restore procedure. The monitor database is not an MDB directory database and does not need a data LDIF. The frontend entry is configuration, not user data.
Save a fuller inventory before the first backup run. Use sudo tee because the shell performs redirection before sudo runs:
sudo ldapsearch -Q -LLL -o ldif-wrap=no -Y EXTERNAL -H ldapi:/// -b cn=config '(objectClass=olcMdbConfig)' dn olcSuffix olcDbDirectory olcDbMaxSize olcDbIndex | sudo tee /root/openldap-mdb-inventory.ldif >/dev/nullThe command exits silently on success when the inventory file is written.
Remember the mapping:
slapcat -n 0exports the configuration database.- Each
olcMdbConfigdatabase entry needs oneslapcat -g -bexport using a primary suffix from that entry. - Use
-gso glued subordinate databases are not duplicated across backup files.
Expected Backup Layout
Create the root directory with restrictive permissions.
sudo install -d -o root -g root -m 0700 /var/backups/openldapUse one atomic directory per backup:
/var/backups/openldap/
└── 20260714T072417Z/
├── cn-config.ldif.gz
├── cn-config-entry-count.txt
├── mdb-01.ldif.gz
├── databases.tsv
├── entry-counts.tsv
├── database-inventory.ldif
├── system-inventory.txt
├── system-files.tar.gz
└── SHA256SUMSWhen replication is enabled, expect an additional mdb-02.ldif.gz for cn=accesslog.
| File | Purpose |
|---|---|
cn-config.ldif.gz |
Complete dynamic OpenLDAP configuration |
cn-config-entry-count.txt |
Expected cn=config entry count for validation |
mdb-NN.ldif.gz |
One logical backup per physical MDB database |
databases.tsv |
Maps backup files to database DNs, suffixes, and directories |
entry-counts.tsv |
Expected entry count per MDB backup file |
database-inventory.ldif |
Original database DNs and settings |
system-inventory.txt |
OS, packages, hostname, and OpenLDAP version |
system-files.tar.gz |
TLS files, backup script, and service-level settings |
SHA256SUMS |
Detects missing or changed backup files |
Use UTC timestamps so backups sort predictably across time zones.
Create and Run the OpenLDAP Backup
Install the Backup Script
Install the script at /usr/local/sbin/openldap-backup. The version below was tested on ldap-server.example.com.
#!/usr/bin/env bash
set -Eeuo pipefail
umask 077
BACKUP_ROOT="/var/backups/openldap"
SLAPD_D="/etc/openldap/slapd.d"
RETENTION_DAYS="30"
STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
TMP_DIR="${BACKUP_ROOT}/.${STAMP}.tmp"
FINAL_DIR="${BACKUP_ROOT}/${STAMP}"
cleanup() {
if [[ -d "$TMP_DIR" ]]; then
rm -rf -- "$TMP_DIR"
fi
}
trap cleanup EXIT
install -d -o root -g root -m 0700 "$BACKUP_ROOT"
install -d -o root -g root -m 0700 "$TMP_DIR"
if ! systemctl is-active --quiet slapd; then
echo "slapd is not active; refusing scheduled online backup." >&2
exit 1
fi
{
date -u
hostnamectl
cat /etc/os-release
rpm -q openldap openldap-clients openldap-servers
slapd -VV 2>&1
} > "$TMP_DIR/system-inventory.txt"
ldapsearch \
-Q \
-LLL \
-o ldif-wrap=no \
-Y EXTERNAL \
-H ldapi:/// \
-b cn=config \
'(objectClass=olcMdbConfig)' \
dn olcSuffix olcDbDirectory olcDbMaxSize olcDbIndex \
> "$TMP_DIR/database-inventory.ldif"
if grep -q '^olcSuffix:: ' "$TMP_DIR/database-inventory.ldif"; then
echo "A base64-encoded MDB suffix was found; review the backup parser." >&2
exit 1
fi
slapcat -F "$SLAPD_D" \
-n 0 \
-l "$TMP_DIR/cn-config.ldif"
grep -c '^dn:' "$TMP_DIR/cn-config.ldif" > "$TMP_DIR/cn-config-entry-count.txt"
: > "$TMP_DIR/databases.tsv"
: > "$TMP_DIR/entry-counts.tsv"
index=1
while IFS=$'\t' read -r db_dn primary_suffix suffix_list db_directory; do
[[ -z "$db_dn" ]] && continue
file="$(printf 'mdb-%02d.ldif' "$index")"
slapcat -g -F "$SLAPD_D" \
-b "$primary_suffix" \
-l "$TMP_DIR/$file"
count="$(grep -c '^dn:' "$TMP_DIR/$file" || true)"
printf '%s\t%s\t%s\t%s\n' \
"${file}.gz" "$db_dn" "$suffix_list" "$db_directory" \
>> "$TMP_DIR/databases.tsv"
printf '%s\t%s\t%s\t%s\n' \
"${file}.gz" "$db_dn" "$primary_suffix" "$count" \
>> "$TMP_DIR/entry-counts.tsv"
((index += 1))
done < <(
awk '
/^dn: olcDatabase=\{/ {
if (db_dn != "") {
print db_dn "\t" primary_suffix "\t" suffixes "\t" db_directory
}
db_dn = substr($0, 5)
primary_suffix = ""
suffixes = ""
db_directory = ""
next
}
/^olcDbDirectory: / {
db_directory = substr($0, 17)
next
}
/^olcSuffix: / {
s = substr($0, 12)
if (primary_suffix == "") {
primary_suffix = s
}
suffixes = (suffixes == "" ? s : suffixes "|" s)
next
}
END {
if (db_dn != "") {
print db_dn "\t" primary_suffix "\t" suffixes "\t" db_directory
}
}
' "$TMP_DIR/database-inventory.ldif"
)
if (( index == 1 )); then
echo "No MDB databases were discovered." >&2
exit 1
fi
TAR_PATHS=()
for path in \
/etc/openldap/certs \
/etc/sysconfig/slapd \
/etc/systemd/system/slapd.service.d \
/usr/local/sbin/openldap-backup \
/etc/systemd/system/openldap-backup.service \
/etc/systemd/system/openldap-backup.timer; do
if [[ -e "$path" ]]; then
TAR_PATHS+=("$path")
fi
done
if (( ${#TAR_PATHS[@]} > 0 )); then
tar \
--xattrs \
--selinux \
--acls \
-czf "$TMP_DIR/system-files.tar.gz" \
"${TAR_PATHS[@]}"
fi
gzip -9 "$TMP_DIR"/cn-config.ldif "$TMP_DIR"/mdb-*.ldif
(
cd "$TMP_DIR"
shopt -s nullglob
sum_files=(
*.gz
databases.tsv
entry-counts.tsv
cn-config-entry-count.txt
system-inventory.txt
database-inventory.ldif
)
if (( ${#sum_files[@]} > 0 )); then
sha256sum "${sum_files[@]}" > SHA256SUMS
fi
)
mv "$TMP_DIR" "$FINAL_DIR"
trap - EXIT
find "$BACKUP_ROOT" \
-mindepth 1 \
-maxdepth 1 \
-type d \
-name '20*T*Z' \
-mtime "+${RETENTION_DAYS}" \
-exec rm -rf -- {} +
echo "OpenLDAP backup completed: $FINAL_DIR"The script enables strict shell error handling, creates a temporary directory, refuses to run when slapd is down, discovers every olcMdbConfig database entry, exports cn=config and one LDIF per physical MDB database with slapcat -g, archives TLS paths, the backup script, and systemd units when present, compresses LDIF exports, writes checksums, renames the directory atomically, and applies retention only after success.
Save the script to a temporary path, then install it:
sudo install -o root -g root -m 0700 /tmp/openldap-backup /usr/local/sbin/openldap-backupIf the file is already at the final path, set ownership and mode instead:
sudo chown root:root /usr/local/sbin/openldap-backupsudo chmod 0700 /usr/local/sbin/openldap-backupOnly root should be able to read or execute the backup script.
How the Database Parser Works
OpenLDAP can attach multiple olcSuffix values to one olcMdbConfig database. Dumping every suffix line separately would export the same physical database more than once.
The script therefore:
- Parses each
olcDatabase={N}mdb,cn=configentry fromdatabase-inventory.ldif. - Records every suffix and
olcDbDirectoryfor that entry indatabases.tsv, using|between values when more than one suffix is configured. - Selects the first
olcSuffixvalue as the primary export target. - Runs
slapcat -g -F /etc/openldap/slapd.d -b "<primary-suffix>"once per database.
Do not split a DN on commas in bash—dc=example,dc=com must stay intact. The parser therefore stores the primary suffix as its own field rather than deriving it with ${suffix_list%%,*}.
Database numbers such as {2} are discovered from the live configuration and stored in the manifest—they are not hard-coded in the script.
Use the same -g flag during restore so subordinate gluing does not pull in unrelated databases. slapcat -g exports only the selected database; slapadd -g imports only into the selected database without processing glued subordinates.
sudo slapadd -g -F /etc/openldap/slapd.d \
-b "dc=example,dc=com" \
-l /root/openldap-restore/mdb-01.ldifRun the First Backup
Run the script once before you schedule it.
sudo /usr/local/sbin/openldap-backupSample output:
tar: Removing leading `/' from member names
OpenLDAP backup completed: /var/backups/openldap/20260714T072417ZVerify the Backup
List the newest backup directory name:
sudo find /var/backups/openldap -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | sort | tail -1Sample output:
20260714T072417ZVerify checksums inside that directory. The backup root is mode 0700, so run the check through a root shell:
sudo bash -c 'cd /var/backups/openldap/20260714T072417Z && sha256sum -c SHA256SUMS'Sample output:
cn-config.ldif.gz: OK
mdb-01.ldif.gz: OK
system-files.tar.gz: OK
databases.tsv: OK
entry-counts.tsv: OK
cn-config-entry-count.txt: OK
system-inventory.txt: OK
database-inventory.ldif: OKInspect the database manifest:
sudo column -t -s $'\t' /var/backups/openldap/20260714T072417Z/databases.tsvSample output:
mdb-01.ldif.gz olcDatabase={2}mdb,cn=config dc=example,dc=com /var/lib/ldapCheck the configuration export contains cn=config entries:
sudo zgrep -m 5 '^dn:' /var/backups/openldap/20260714T072417Z/cn-config.ldif.gzSample output:
dn: cn=config
dn: cn=module{0},cn=config
dn: cn=schema,cn=config
dn: cn={0}core,cn=schema,cn=config
dn: cn={1}cosine,cn=schema,cn=configConfirm backup file permissions stay root-only:
sudo find /var/backups/openldap/20260714T072417Z -printf '%M %u:%g %p\n'Every file should show mode 600 or stricter and owner root:root. Investigate an unexpected zero-byte LDIF for a database known to contain entries. A deliberately empty MDB database can legitimately produce an entry count of zero.
Automate, Retain and Monitor Backups
Create the systemd Service
Create /etc/systemd/system/openldap-backup.service:
[Unit]
Description=Back up OpenLDAP cn=config and all MDB databases
After=slapd.service
ConditionPathIsDirectory=/etc/openldap/slapd.d
[Service]
Type=oneshot
User=root
Group=root
UMask=0077
ExecStart=/usr/bin/flock -n /var/backups/openldap/.openldap-backup.lock /usr/local/sbin/openldap-backup
Nice=10
IOSchedulingClass=best-effort
IOSchedulingPriority=7
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=true
ProtectHome=true
ReadWritePaths=/var/backups/openldapflock prevents overlapping jobs. The lock file is stored under the backup root so the backup data and its concurrency lock use one explicitly managed writable location. A systemd-native alternative is RuntimeDirectory=openldap-backup with ExecStart=/usr/bin/flock -n /run/openldap-backup/backup.lock /usr/local/sbin/openldap-backup.
I use ProtectSystem=true for this tested unit. An online MDB dump must retain normal access to the database environment and LMDB lock state. Before changing this to ProtectSystem=strict, explicitly allow every configured olcDbDirectory as required and test the service against the live MDB environment.
Create the systemd Timer
Create /etc/systemd/system/openldap-backup.timer:
[Unit]
Description=Run OpenLDAP backup daily
[Timer]
OnCalendar=*-*-* 02:15:00
Persistent=true
RandomizedDelaySec=15m
AccuracySec=1m
Unit=openldap-backup.service
[Install]
WantedBy=timers.targetReload systemd after creating or changing the unit files. Timer and oneshot services use systemctl for daemon-reload, enable, and list-timers:
sudo systemctl daemon-reloadEnable the daily timer immediately:
sudo systemctl enable --now openldap-backup.timerConfirm the timer is scheduled:
systemctl list-timers --all openldap-backup.timerSample output:
NEXT LEFT LAST PASSED UNIT ACTIVATES
Wed 2026-07-15 02:17:57 IST 13h - - openldap-backup.timer openldap-backup.service
1 timers listed.Test the Scheduled Service
Run the service manually once to catch path, permission, or sandbox errors before the first scheduled run:
sudo systemctl start openldap-backup.serviceReview the most recent log lines:
sudo journalctl -u openldap-backup.service -n 20 --no-pagerA timer being active does not prove that its most recent service execution succeeded.
Configure Retention and Off-Host Copies
The example script keeps 30 days of local backups. Local retention alone is not enough.
| Copy | Example retention |
|---|---|
| Local daily backup | 14–30 days |
| Remote daily backup | 30–90 days |
| Weekly backup | 8–12 weeks |
| Monthly backup | 12 months |
| Pre-upgrade backup | Until upgrade rollback window closes |
Retention runs only after every expected MDB export succeeded, compression succeeded, the manifest was written, checksums were generated, and the backup directory was finalized.
Recommend encrypted off-host copy, restricted backup credentials, monitoring for missing daily backups, capacity alerts, periodic checksum verification, and a documented deletion policy. Do not keep the only backup on ldap-server.
Monitor the Last Successful Backup
Confirm the timer is still scheduled:
systemctl list-timers --all openldap-backup.timerA missing NEXT time or an old LAST value can mean the timer is disabled or jobs are failing.
Inspect the most recent oneshot service result:
systemctl show openldap-backup.service -p Result -p ExecMainStatus -p ExecMainStartTimestamp -p ExecMainExitTimestampResult=success and ExecMainStatus=0 indicate the last backup run completed cleanly.
Review recent backup log lines when the timer is active but no new directory appears:
sudo journalctl -u openldap-backup.service --since "7 days ago" --no-pagerMonitor last successful backup timestamp, backup directory size, expected number of MDB files, checksum verification, entry-count changes, available disk space, timer failures, retention execution, off-host transfer success, and age of the last tested backup.
Choose the Correct Restore Procedure
Select the restore path that matches your failure mode before you stop slapd or decompress LDIF files.
| Recovery scenario | Recommended action |
|---|---|
| Backup creation only | Complete Plan What to Back Up through Automate, Retain and Monitor Backups |
One data MDB is damaged; cn=config is healthy |
Restore One MDB Database |
slapd.d is missing or corrupted |
Perform Complete Disaster Recovery |
| Entire host was rebuilt | Perform Complete Disaster Recovery |
Delta-syncrepl provider with cn=accesslog failed |
Restore coordinated main and accesslog backups from the same recovery point |
slapadd requires an offline database. Stop slapd before every slapadd import. The target olcDbDirectory must be empty—slapadd creates a fresh MDB environment and commonly fails with MDB_KEYEXIST when data already exists.
Verify SHA256SUMS before you decompress or import any backup file. A corrupted archive can waste hours of restore work and may leave the server in a worse state.
Preserve damaged configuration and database directories by renaming them instead of deleting them. A timestamped .failed-* or .pre-restore-* suffix keeps the original files available for comparison.
Do not use slapadd -c, -q, or -w in routine recovery. Avoid rm -rf on /etc/openldap/slapd.d or /var/lib/ldap during production restore.
For delta-syncrepl, never restore a main provider database without its matching cn=accesslog recovery point unless you deliberately plan to recreate replication state.
When the target host differs in OpenLDAP version, operating-system family, or hostname design from the backup source, follow OpenLDAP migration examples instead of a straight LDIF restore.
Restore One MDB Database
Use this procedure when cn=config is healthy, only one MDB database is damaged, the restored suffix matches the existing configuration, and package and backend compatibility have not changed.
Do not use this procedure to restore only the main provider database when delta-syncrepl depends on a paired accesslog. Restore both databases from the same coordinated backup, or deliberately recreate the accesslog and follow the replication recovery procedure.
Verify the backup checksums first so you know the archive is intact before decompression:
sudo bash -c 'cd /var/backups/openldap/20260714T072417Z && sha256sum -c SHA256SUMS'Every listed file should report OK.
Create a root-only working directory for the decompressed LDIF:
sudo install -d -m 0700 /root/openldap-restoreDecompress the target suffix into that directory:
sudo gzip -dc /var/backups/openldap/20260714T072417Z/mdb-01.ldif.gz | sudo tee /root/openldap-restore/example-com.ldif >/dev/nullConfirm the suffix mapping in databases.tsv before you import anything.
Stop slapd because slapadd requires an offline database:
sudo systemctl stop slapdPreserve the damaged database directory instead of deleting it:
sudo mv /var/lib/ldap /var/lib/ldap.failed-$(date -u +%Y%m%dT%H%M%SZ)The rename keeps the failed MDB files available for later comparison.
Create an empty target directory with the correct owner:
sudo install -d -o ldap -g ldap -m 0700 /var/lib/ldapImport the decompressed LDIF into the empty database:
sudo slapadd -g -F /etc/openldap/slapd.d -b "dc=example,dc=com" -l /root/openldap-restore/example-com.ldifDo not use -c, -q, or -w by default.
slapadd creates files as root, so restore ownership before validation:
sudo chown -R ldap:ldap /var/lib/ldapApply the expected SELinux label on the rebuilt database path:
sudo restorecon -Rv /var/lib/ldapValidate while the service is still stopped:
sudo slaptest -F /etc/openldap/slapd.d -uCount restored entries offline and compare with entry-counts.tsv:
sudo slapcat -g -F /etc/openldap/slapd.d -b "dc=example,dc=com" | grep -c '^dn:'Start slapd only after validation succeeds:
sudo systemctl start slapdConfirm the service reached the active state:
sudo systemctl status slapd --no-pagerThe status line should show active (running).
Query the root DSE to confirm the listener exposes the expected naming contexts:
ldapsearch -x -ZZ -H ldap://ldap-server.example.com -b "" -s base -LLL namingContexts supportedLDAPVersionThe result should list dc=example,dc=com and report LDAP version 3. For deeper LDAP, TLS, SSSD, and replication checks, continue with Validate the Restored Directory.
Perform Complete Disaster Recovery
Use this procedure when the server was rebuilt, cn=config is damaged or missing, or every database directory must be recreated. For a different OpenLDAP version, backend, operating-system family, or hostname design, use OpenLDAP migration examples instead.
Verify the Backup and Install Compatible Packages
Confirm the backup set is complete before you touch the live server:
sudo bash -c 'cd /var/backups/openldap/20260714T072417Z && sha256sum -c SHA256SUMS'Create a root-only restore workspace:
sudo install -d -m 0700 /root/openldap-restoreDecompress the configuration export first:
sudo gzip -dc /var/backups/openldap/20260714T072417Z/cn-config.ldif.gz | sudo tee /root/openldap-restore/cn-config.ldif >/dev/nullDecompress each mdb-NN.ldif.gz file according to databases.tsv using the same gzip -dc … | sudo tee … pattern.
Record package versions from system-inventory.txt and compare them on the target host:
rpm -q openldap openldap-clients openldap-serversConfirm the installed slapd build matches the backup environment closely enough:
slapd -VV 2>&1The package version does not always need to be byte-for-byte identical for an LDIF restore, but the target must provide the required backend, modules, overlays, configuration attributes, and file paths. Standard package modules are covered by installing compatible OpenLDAP packages first. On a host with custom modules, restore or reinstall those module files before importing or validating cn=config, because processing a configuration that references an unavailable module can fail.
Stop slapd and Preserve Existing Data
Stop the directory service before any import:
sudo systemctl stop slapdsystemctl is-active slapdSample output:
inactiveMove the current dynamic configuration aside instead of overwriting it in place:
sudo mv /etc/openldap/slapd.d /etc/openldap/slapd.d.pre-restore-$(date -u +%Y%m%dT%H%M%SZ)Create a fresh empty configuration directory for the import:
sudo install -d -o ldap -g ldap -m 0750 /etc/openldap/slapd.dRestore cn=config and External Files
Load the backed-up configuration tree into the empty slapd.d directory:
sudo slapadd -n 0 -F /etc/openldap/slapd.d -l /root/openldap-restore/cn-config.ldifRestore ownership on the configuration database:
sudo chown -R ldap:ldap /etc/openldap/slapd.dApply the slapd_db_t context expected under /etc/openldap/slapd.d:
sudo restorecon -Rv /etc/openldap/slapd.dA rebuilt host may not yet have certificate, private-key, module, or service files referenced by the restored configuration. Restore external files before you treat the first slaptest failure as a bad cn=config LDIF.
Extract the archived TLS, service, and backup-unit files. A completed backup from the tested script includes this archive; let a missing file fail visibly instead of skipping private keys and unit files silently:
sudo tar --xattrs --acls --selinux \
-xzf /var/backups/openldap/20260714T072417Z/system-files.tar.gz \
-C /An unprivileged shell cannot test -f under the mode 0700 backup root, so do not wrap this step in a normal-user if [[ -f … ]] condition.
Reapply SELinux labels across the restored OpenLDAP paths:
sudo restorecon -Rv /etc/openldapReload systemd so restored unit files are visible:
sudo systemctl daemon-reloadConfirm private-key permissions after extraction.
Read every configured MDB path from the restored cn=config before you validate or import data:
sudo slapcat -n 0 -F /etc/openldap/slapd.d | grep -E '^(dn: olcDatabase|olcSuffix:|olcDbDirectory:)'Compare the result with database-inventory.ldif and databases.tsv before you recreate any directory.
Recreate MDB Directories
OpenLDAP requires configured database directories to exist with suitable permissions, and slaptest parses and sanity-checks the backend configuration. On a rebuilt host, create every olcDbDirectory before the first slaptest.
For each path such as /var/lib/ldap or /var/lib/ldap-accesslog:
- Rename any existing directory instead of deleting it.
- Create a new empty directory owned by
ldap:ldap. - Apply the expected SELinux context.
Example for the lab main directory suffix:
sudo install -d -o ldap -g ldap -m 0700 /var/lib/ldapThat creates an empty target for dc=example,dc=com before slapadd runs. /var/lib/ldap is covered by the default SELinux policy, so restorecon alone is enough for that path.
When cn=accesslog is configured, create its separate changelog directory as well:
sudo install -d -o ldap -g ldap -m 0700 /var/lib/ldap-accesslogThe default policy does not normally label /var/lib/ldap-accesslog as slapd_db_t, so add a persistent mapping. On a minimal RHEL-family installation, semanage may not be installed:
if ! command -v semanage >/dev/null 2>&1; then
sudo dnf install -y policycoreutils-python-utils
fiThe semanage command is provided by policycoreutils-python-utils on current RHEL-family systems. Install it before defining a persistent SELinux context for a custom MDB directory. RHEL 10 documentation lists policycoreutils-python-utils as a prerequisite for workflows that use semanage.
sudo semanage fcontext -a -t slapd_db_t '/var/lib/ldap-accesslog(/.*)?'If the mapping already exists from a previous host build, modify it instead:
sudo semanage fcontext -m -t slapd_db_t '/var/lib/ldap-accesslog(/.*)?'Apply the file context to the new directory:
sudo restorecon -Rv /var/lib/ldap-accesslogApply the default database label on the main path:
sudo restorecon -Rv /var/lib/ldapVerify both directories carry slapd_db_t:
ls -Zd /var/lib/ldap /var/lib/ldap-accesslogRepeat the same semanage fcontext pattern for any other custom olcDbDirectory value discovered from the restored configuration.
For an MDB directory outside the distribution default /var/lib/ldap path, define a persistent slapd_db_t file-context mapping with semanage fcontext before running restorecon.
Validate Configuration
With every olcDbDirectory in place, confirm the restored configuration parses offline:
sudo slaptest -F /etc/openldap/slapd.d -uA successful run reports config file testing succeeded and exits without configuration errors.
Count the restored cn=config entries offline and compare with the backup manifest:
sudo slapcat -n 0 -F /etc/openldap/slapd.d | grep -c '^dn:'The result should match cn-config-entry-count.txt from the backup directory.
Do not continue when slaptest reports configuration errors.
Restore Every MDB Database
Import the main directory data into the empty database path:
sudo slapadd -g -F /etc/openldap/slapd.d -b "dc=example,dc=com" -l /root/openldap-restore/mdb-01.ldifslapadd rebuilds indexes as it loads the LDIF. Stop on the first reported error.
When cn=accesslog is present, import its backup into the matching empty directory:
sudo slapadd -g -F /etc/openldap/slapd.d -b "cn=accesslog" -l /root/openldap-restore/mdb-02.ldifRestore by suffix rather than historical database number.
Validate and Start slapd
slapadd runs as root, so reset ownership on configuration and data paths before startup:
sudo chown -R ldap:ldap /etc/openldap/slapd.d /var/lib/ldapReapply SELinux labels on the restored tree. Custom database paths such as /var/lib/ldap-accesslog need their semanage fcontext mapping in place before this step succeeds:
sudo restorecon -Rv /etc/openldap /var/lib/ldapInclude every additional MDB directory such as /var/lib/ldap-accesslog in both chown and restorecon when it exists.
Run one more offline configuration check after the data import:
sudo slaptest -F /etc/openldap/slapd.d -uCount imported entries for the main suffix and compare with entry-counts.tsv:
sudo slapcat -g -F /etc/openldap/slapd.d -b "dc=example,dc=com" | grep -c '^dn:'The offline count should match the manifest value for mdb-01.ldif.gz.
Start the directory service only after offline validation succeeds:
sudo systemctl start slapdConfirm the service reached the active state:
sudo systemctl status slapd --no-pagerThe status line should show active (running).
Re-enable scheduled backups after disaster recovery. The system-files.tar.gz archive contains the timer unit file but not the timers.target.wants enablement symlink created by systemctl enable.
sudo systemctl enable --now openldap-backup.timerEnabling the timer schedules future runs; it does not necessarily execute the backup service immediately.
Run one backup manually to prove the restored host can create a new archive:
sudo systemctl start openldap-backup.serviceConfirm the schedule and the post-recovery service result:
systemctl list-timers --all openldap-backup.timersystemctl show openldap-backup.service -p Result -p ExecMainStatus -p ExecMainStartTimestamp -p ExecMainExitTimestampResult=success and ExecMainStatus=0 should reflect this manual run, not only an older execution.
List the newest backup directory:
sudo find /var/backups/openldap -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | sort | tail -1Validate checksums inside that directory:
sudo bash -c 'cd /var/backups/openldap/LATEST_STAMP && sha256sum -c SHA256SUMS'Replace LATEST_STAMP with the directory name from the previous command.
Validate the Restored Directory
| Test | What it validates |
|---|---|
slaptest |
Configuration can be parsed |
| Offline entry count | Expected data was imported |
| Service status | slapd can start |
| Root DSE search | Listener and naming contexts |
| StartTLS handshake | Certificates, CA, and hostname |
| SSSD reader search | Service account, ACL, and POSIX attributes |
| Known-group search | Membership data |
| Password bind | Password hashes and authentication |
| User modification | ACL and write behavior |
| Overlay behavior | memberof, ppolicy, refint, and others |
| Replication state | Synchronization health |
| Backup timer enabled and tested | Future backups remain scheduled after DR restore |
Do not declare recovery successful merely because slapd is active.
Validate LDAP and StartTLS
Query the root DSE to confirm the listener exposes the expected naming contexts:
ldapsearch -x -ZZ -H ldap://ldap-server.example.com -b "" -s base -LLL namingContexts supportedLDAPVersionThe result should list dc=example,dc=com and report LDAP version 3.
Test password authentication against the restored hash:
ldapwhoami -x -ZZ -H ldap://ldap-server.example.com -D "uid=jdoe,ou=people,dc=example,dc=com" -WA successful bind returns dn:uid=jdoe,ou=people,dc=example,dc=com.
Verify the server certificate and StartTLS trust path:
openssl s_client -starttls ldap -connect ldap-server.example.com:389 -servername ldap-server.example.com -CAfile /etc/openldap/certs/example-ldap-ca.crt -verify_return_errorThe command should complete with Verify return code: 0 (ok).
Validate SSSD and Application Access
Bind as the restored SSSD reader from ou=people, the same base the ACL article grants for POSIX lookups. A search from the suffix root can fail even when the reader may query users below ou=people, because OpenLDAP requires search access to the base entry itself.
ldapsearch -x -ZZ \
-H ldap://ldap-server.example.com \
-D "uid=sssd-reader,ou=service-accounts,dc=example,dc=com" \
-W \
-b "ou=people,dc=example,dc=com" \
-LLL "(uid=jdoe)" \
dn uid uidNumber gidNumber homeDirectory loginShellA matching dn: line with POSIX attributes confirms the main suffix import and reader ACLs survived recovery. See OpenLDAP ACL examples for the reader policy.
From an SSSD client, clear the cache and confirm POSIX lookups. Client configuration is covered in Configure an OpenLDAP client with SSSD on the RHEL family.
Flush cached NSS data so the client does not return stale local results:
sudo sss_cache -ERequest the restored POSIX account through NSS:
getent passwd jdoeA returned line with uid, gid, and home confirms client-side lookup against the restored directory.
ACL behavior belongs in OpenLDAP ACL examples. User and group content checks belong in Manage OpenLDAP users and groups.
Validate Replication
Replication recovery is topology-specific.
For a failed consumer in a provider-consumer deployment, re-seeding from the current provider is often better than restoring an old backup because the provider contains newer directory state. See OpenLDAP provider-consumer replication.
For multi-provider deployments, review olcServerID, olcSyncrepl, olcMultiProvider, contextCSN, and accesslog age before reconnecting a restored node. See OpenLDAP multi-provider replication.
Back up cn=accesslog when it is required for delta-syncrepl recovery or audit retention. Restore it only with the matching main-database recovery point; an older or mismatched accesslog must not be treated as current replication state.
Run an Isolated Recovery Drill
Run an isolated restore drill on a host that is not connected to production replication traffic.
Recommended test procedure:
- Select a recent backup.
- Verify its checksums.
- Record package versions from
system-inventory.txt. - Install compatible packages on
ldap-client. - Use a local root console for the drill. Stop or isolate SSSD only when necessary to prevent the test host from depending on production LDAP or generating unintended production queries.
- Restore
cn=config. - Restore TLS and service files from
system-files.tar.gz. - Review every
olcDbDirectoryand create empty MDB directories with correct ownership and SELinux labels. - Run
slaptest. - Restore every MDB database with
slapadd -g. - Start the isolated
slapd. - Compare entry counts for
cn=configand each MDB file. - Test representative user searches and a password bind.
- Record restore duration.
- Destroy or securely wipe restored production data after the drill.
On ldap-client, checksum verification, slaptest, and an LDAPI search for uid=jdoe succeeded after a full restore from 20260714T072417Z. StartTLS against localhost failed in the drill because the restored certificate names ldap-server.example.com; that is expected on an isolated test host unless you substitute test certificates.
| Field | Example result |
|---|---|
| Backup timestamp | 20260714T072417Z |
| Configuration restore | Pass |
| Main MDB restore | Pass |
| Entry-count comparison | 13 entries in manifest and offline slapcat |
| Password bind | Test with production hostname or substituted certs |
| Restore duration | Record per environment |
Repeat recovery drills after OpenLDAP version changes, database layout changes, TLS path changes, replication topology changes, or backup-script changes.
Troubleshoot Backup and Restore
| Error or symptom | Likely cause | Recommended action |
|---|---|---|
slapcat: bad configuration directory |
Wrong -F path or damaged slapd.d |
Verify /etc/openldap/slapd.d and run slaptest |
| Duplicate data across MDB backup files | slapcat without -g on glued subordinates |
Add -g to slapcat and slapadd; export once per olcMdbConfig entry |
slaptest fails immediately after cn=config import |
External TLS or module files not restored yet, or MDB directories missing | Extract system-files.tar.gz, create every olcDbDirectory, add semanage fcontext for custom paths, run restorecon, then rerun slaptest |
| Additional suffix is missing | Script assumes one MDB database | Discover every olcMdbConfig entry |
cn=accesslog was not restored |
Accesslog was not separately dumped | Include every MDB suffix in the manifest |
MDB_KEYEXIST during restore |
Target database is not empty | Preserve and recreate an empty MDB directory |
slapadd reports duplicate entries |
Import was run more than once | Start with an empty database |
| Unknown object class | Configuration or schema was not restored first | Restore and validate cn=config before data |
| Unknown attribute type | Schema or overlay is missing | Compare restored configuration and package modules |
| Module cannot be loaded | Package or module path differs | Install the required module or review migration compatibility |
slapadd creates root-owned files |
Restore ran as root | Apply ldap:ldap ownership before startup |
slapd fails with permission denied |
DAC or SELinux labels are wrong | Run chown and restorecon; for custom olcDbDirectory paths outside /var/lib/ldap, add semanage fcontext for slapd_db_t first; inspect AVC logs |
| TLS fails after restore | Certificate files were not restored or name differs | Restore or reissue certificates and verify SAN |
slapd starts but users are absent |
Wrong suffix file was imported | Check databases.tsv and naming contexts |
| Password bind fails | Password data, ACLs, TLS, or policy is incomplete | Test each layer separately |
ldapadd rejects slapcat output |
Backup contains operational attributes | Restore with slapadd, not ldapadd |
| Backup script deletes old backups after failure | Retention runs before final success | Move retention after atomic finalization |
| Timer runs twice | Backup processes overlap | Use flock |
| Timer is active but no backup exists | Service execution failed | Inspect the service result and journal |
| Restored multi-provider node conflicts | Old server ID or CSN state was reintroduced | Isolate the node and follow replication recovery procedures |
Backup fails after enabling ProtectSystem=strict |
The backup root or an MDB lock path is read-only inside the unit namespace | Add the required paths with ReadWritePaths=, or retain the tested ProtectSystem=true configuration |
Useful diagnostics when a backup or restore step fails unexpectedly:
sudo slaptest -uParse the offline configuration without starting slapd.
sudo journalctl -u slapd -n 100 --no-pagerReview recent server startup or backend errors.
sudo ausearch -m AVC -ts recentCheck for SELinux denials against slapd, database paths, or certificate files.
Avoid these shortcuts in the normal restore procedure:
slapadd -c
slapadd -q
slapadd -w
rm -rf /etc/openldap/slapd.d/*
rm -rf /var/lib/ldap/*Each can be valid in a narrowly understood situation, but none should be the default recovery instruction.
References
- OpenLDAP 2.6 Administrator's Guide — Maintenance
- slapcat(8)
- slapadd(8)
- slaptest(8)
- slapd-config(5)
- slapd-mdb(5)
- systemd.service(5)
- systemd.timer(5)
Summary
You can back up and restore a complete OpenLDAP deployment by exporting cn=config with slapcat -n 0, discovering every olcMdbConfig database dynamically, dumping each physical MDB database with slapcat -g -b, archiving TLS and service files separately, writing manifests and checksums, automating the workflow with a hardened systemd timer, applying retention only after success, and proving recovery with an isolated restore drill.

