OpenLDAP Backup and Restore with slapcat and slapadd

Back up and restore OpenLDAP cn=config and all MDB databases using slapcat, slapadd, systemd timers, retention, checksums, and tested recovery.

Published

Updated

Read time 30 min read

Reviewed byDeepak Prasad

OpenLDAP cn=config and MDB backup and restore using slapcat and slapadd

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=config configuration 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-servers 2.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:

IMPORTANT
A backup is not proven until it has been restored and validated. Successful execution of 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:

text
/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=config

The 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:

  • slapcat preserves stored operational attributes such as entryUUID and entryCSN
  • Its output is intended for slapadd
  • LDIF is portable across compatible systems
  • Suffix-based commands with -g avoid 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:

  1. Pause provisioning applications.
  2. Temporarily block writes at the application layer.
  3. Temporarily make affected databases read-only.
  4. Stop slapd for 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:

text
Daily: online logical backup
Weekly or before major changes: quiesced backup
Quarterly: full restore drill

Adjust the schedule to your required RPO and RTO.

WARNING
Delta-syncrepl exception: When a provider uses a separate 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.

WARNING
The system-file archive can contain the LDAP server private key. Store it only in encrypted, access-controlled backup storage and never attach it to support tickets or publish it with article output.

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.

bash
sudo ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b cn=config '(objectClass=olcDatabaseConfig)' dn olcDatabase olcSuffix

That query shows every database entry under cn=config, including the config, monitor, and frontend databases.

To list MDB data databases only, filter on olcMdbConfig:

bash
sudo ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b cn=config '(objectClass=olcMdbConfig)' dn olcSuffix olcDbDirectory olcDbMaxSize

Sample output:

output
dn: olcDatabase={2}mdb,cn=config
olcDbDirectory: /var/lib/ldap
olcSuffix: dc=example,dc=com

On 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:

bash
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/null

The command exits silently on success when the inventory file is written.

Remember the mapping:

  • slapcat -n 0 exports the configuration database.
  • Each olcMdbConfig database entry needs one slapcat -g -b export using a primary suffix from that entry.
  • Use -g so glued subordinate databases are not duplicated across backup files.

Expected Backup Layout

Create the root directory with restrictive permissions.

bash
sudo install -d -o root -g root -m 0700 /var/backups/openldap

Use one atomic directory per backup:

text
/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
    └── SHA256SUMS

When 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.

bash
#!/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:

bash
sudo install -o root -g root -m 0700 /tmp/openldap-backup /usr/local/sbin/openldap-backup

If the file is already at the final path, set ownership and mode instead:

bash
sudo chown root:root /usr/local/sbin/openldap-backup
bash
sudo chmod 0700 /usr/local/sbin/openldap-backup

Only 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:

  1. Parses each olcDatabase={N}mdb,cn=config entry from database-inventory.ldif.
  2. Records every suffix and olcDbDirectory for that entry in databases.tsv, using | between values when more than one suffix is configured.
  3. Selects the first olcSuffix value as the primary export target.
  4. 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.

bash
sudo slapadd -g -F /etc/openldap/slapd.d \
  -b "dc=example,dc=com" \
  -l /root/openldap-restore/mdb-01.ldif

Run the First Backup

Run the script once before you schedule it.

bash
sudo /usr/local/sbin/openldap-backup

Sample output:

output
tar: Removing leading `/' from member names
OpenLDAP backup completed: /var/backups/openldap/20260714T072417Z

Verify the Backup

List the newest backup directory name:

bash
sudo find /var/backups/openldap -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | sort | tail -1

Sample output:

output
20260714T072417Z

Verify checksums inside that directory. The backup root is mode 0700, so run the check through a root shell:

bash
sudo bash -c 'cd /var/backups/openldap/20260714T072417Z && sha256sum -c SHA256SUMS'

Sample output:

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: OK

Inspect the database manifest:

bash
sudo column -t -s $'\t' /var/backups/openldap/20260714T072417Z/databases.tsv

Sample output:

output
mdb-01.ldif.gz  olcDatabase={2}mdb,cn=config  dc=example,dc=com  /var/lib/ldap

Check the configuration export contains cn=config entries:

bash
sudo zgrep -m 5 '^dn:' /var/backups/openldap/20260714T072417Z/cn-config.ldif.gz

Sample output:

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=config

Confirm backup file permissions stay root-only:

bash
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:

ini
[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/openldap

flock 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:

ini
[Unit]
Description=Run OpenLDAP backup daily

[Timer]
OnCalendar=*-*-* 02:15:00
Persistent=true
RandomizedDelaySec=15m
AccuracySec=1m
Unit=openldap-backup.service

[Install]
WantedBy=timers.target

Reload systemd after creating or changing the unit files. Timer and oneshot services use systemctl for daemon-reload, enable, and list-timers:

bash
sudo systemctl daemon-reload

Enable the daily timer immediately:

bash
sudo systemctl enable --now openldap-backup.timer

Confirm the timer is scheduled:

bash
systemctl list-timers --all openldap-backup.timer

Sample output:

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:

bash
sudo systemctl start openldap-backup.service

Review the most recent log lines:

bash
sudo journalctl -u openldap-backup.service -n 20 --no-pager

A 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:

bash
systemctl list-timers --all openldap-backup.timer

A 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:

bash
systemctl show openldap-backup.service -p Result -p ExecMainStatus -p ExecMainStartTimestamp -p ExecMainExitTimestamp

Result=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:

bash
sudo journalctl -u openldap-backup.service --since "7 days ago" --no-pager

Monitor 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
WARNING
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:

bash
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:

bash
sudo install -d -m 0700 /root/openldap-restore

Decompress the target suffix into that directory:

bash
sudo gzip -dc /var/backups/openldap/20260714T072417Z/mdb-01.ldif.gz | sudo tee /root/openldap-restore/example-com.ldif >/dev/null

Confirm the suffix mapping in databases.tsv before you import anything.

Stop slapd because slapadd requires an offline database:

bash
sudo systemctl stop slapd

Preserve the damaged database directory instead of deleting it:

bash
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:

bash
sudo install -d -o ldap -g ldap -m 0700 /var/lib/ldap

Import the decompressed LDIF into the empty database:

bash
sudo slapadd -g -F /etc/openldap/slapd.d -b "dc=example,dc=com" -l /root/openldap-restore/example-com.ldif

Do not use -c, -q, or -w by default.

slapadd creates files as root, so restore ownership before validation:

bash
sudo chown -R ldap:ldap /var/lib/ldap

Apply the expected SELinux label on the rebuilt database path:

bash
sudo restorecon -Rv /var/lib/ldap

Validate while the service is still stopped:

bash
sudo slaptest -F /etc/openldap/slapd.d -u

Count restored entries offline and compare with entry-counts.tsv:

bash
sudo slapcat -g -F /etc/openldap/slapd.d -b "dc=example,dc=com" | grep -c '^dn:'

Start slapd only after validation succeeds:

bash
sudo systemctl start slapd

Confirm the service reached the active state:

bash
sudo systemctl status slapd --no-pager

The status line should show active (running).

Query the root DSE to confirm the listener exposes the expected naming contexts:

bash
ldapsearch -x -ZZ -H ldap://ldap-server.example.com -b "" -s base -LLL namingContexts supportedLDAPVersion

The 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:

bash
sudo bash -c 'cd /var/backups/openldap/20260714T072417Z && sha256sum -c SHA256SUMS'

Create a root-only restore workspace:

bash
sudo install -d -m 0700 /root/openldap-restore

Decompress the configuration export first:

bash
sudo gzip -dc /var/backups/openldap/20260714T072417Z/cn-config.ldif.gz | sudo tee /root/openldap-restore/cn-config.ldif >/dev/null

Decompress 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:

bash
rpm -q openldap openldap-clients openldap-servers

Confirm the installed slapd build matches the backup environment closely enough:

bash
slapd -VV 2>&1

The 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:

bash
sudo systemctl stop slapd
bash
systemctl is-active slapd

Sample output:

output
inactive

Move the current dynamic configuration aside instead of overwriting it in place:

bash
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:

bash
sudo install -d -o ldap -g ldap -m 0750 /etc/openldap/slapd.d

Restore cn=config and External Files

Load the backed-up configuration tree into the empty slapd.d directory:

bash
sudo slapadd -n 0 -F /etc/openldap/slapd.d -l /root/openldap-restore/cn-config.ldif

Restore ownership on the configuration database:

bash
sudo chown -R ldap:ldap /etc/openldap/slapd.d

Apply the slapd_db_t context expected under /etc/openldap/slapd.d:

bash
sudo restorecon -Rv /etc/openldap/slapd.d

A 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:

bash
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:

bash
sudo restorecon -Rv /etc/openldap

Reload systemd so restored unit files are visible:

bash
sudo systemctl daemon-reload

Confirm private-key permissions after extraction.

Read every configured MDB path from the restored cn=config before you validate or import data:

bash
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:

  1. Rename any existing directory instead of deleting it.
  2. Create a new empty directory owned by ldap:ldap.
  3. Apply the expected SELinux context.

Example for the lab main directory suffix:

bash
sudo install -d -o ldap -g ldap -m 0700 /var/lib/ldap

That 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:

bash
sudo install -d -o ldap -g ldap -m 0700 /var/lib/ldap-accesslog

The 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:

bash
if ! command -v semanage >/dev/null 2>&1; then
    sudo dnf install -y policycoreutils-python-utils
fi

The 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.

bash
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:

bash
sudo semanage fcontext -m -t slapd_db_t '/var/lib/ldap-accesslog(/.*)?'

Apply the file context to the new directory:

bash
sudo restorecon -Rv /var/lib/ldap-accesslog

Apply the default database label on the main path:

bash
sudo restorecon -Rv /var/lib/ldap

Verify both directories carry slapd_db_t:

bash
ls -Zd /var/lib/ldap /var/lib/ldap-accesslog

Repeat 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:

bash
sudo slaptest -F /etc/openldap/slapd.d -u

A 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:

bash
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:

bash
sudo slapadd -g -F /etc/openldap/slapd.d -b "dc=example,dc=com" -l /root/openldap-restore/mdb-01.ldif

slapadd 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:

bash
sudo slapadd -g -F /etc/openldap/slapd.d -b "cn=accesslog" -l /root/openldap-restore/mdb-02.ldif

Restore 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:

bash
sudo chown -R ldap:ldap /etc/openldap/slapd.d /var/lib/ldap

Reapply 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:

bash
sudo restorecon -Rv /etc/openldap /var/lib/ldap

Include 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:

bash
sudo slaptest -F /etc/openldap/slapd.d -u

Count imported entries for the main suffix and compare with entry-counts.tsv:

bash
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:

bash
sudo systemctl start slapd

Confirm the service reached the active state:

bash
sudo systemctl status slapd --no-pager

The 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.

bash
sudo systemctl enable --now openldap-backup.timer

Enabling 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:

bash
sudo systemctl start openldap-backup.service

Confirm the schedule and the post-recovery service result:

bash
systemctl list-timers --all openldap-backup.timer
bash
systemctl show openldap-backup.service -p Result -p ExecMainStatus -p ExecMainStartTimestamp -p ExecMainExitTimestamp

Result=success and ExecMainStatus=0 should reflect this manual run, not only an older execution.

List the newest backup directory:

bash
sudo find /var/backups/openldap -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | sort | tail -1

Validate checksums inside that directory:

bash
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:

bash
ldapsearch -x -ZZ -H ldap://ldap-server.example.com -b "" -s base -LLL namingContexts supportedLDAPVersion

The result should list dc=example,dc=com and report LDAP version 3.

Test password authentication against the restored hash:

bash
ldapwhoami -x -ZZ -H ldap://ldap-server.example.com -D "uid=jdoe,ou=people,dc=example,dc=com" -W

A successful bind returns dn:uid=jdoe,ou=people,dc=example,dc=com.

Verify the server certificate and StartTLS trust path:

bash
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_error

The 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.

bash
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 loginShell

A 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:

bash
sudo sss_cache -E

Request the restored POSIX account through NSS:

bash
getent passwd jdoe

A 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.

WARNING
Do not connect a restored copy to the production network using the same LDAP identity, replication server ID, or service IP until the test host is isolated and the topology has been reviewed.

Recommended test procedure:

  1. Select a recent backup.
  2. Verify its checksums.
  3. Record package versions from system-inventory.txt.
  4. Install compatible packages on ldap-client.
  5. 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.
  6. Restore cn=config.
  7. Restore TLS and service files from system-files.tar.gz.
  8. Review every olcDbDirectory and create empty MDB directories with correct ownership and SELinux labels.
  9. Run slaptest.
  10. Restore every MDB database with slapadd -g.
  11. Start the isolated slapd.
  12. Compare entry counts for cn=config and each MDB file.
  13. Test representative user searches and a password bind.
  14. Record restore duration.
  15. 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:

bash
sudo slaptest -u

Parse the offline configuration without starting slapd.

bash
sudo journalctl -u slapd -n 100 --no-pager

Review recent server startup or backend errors.

bash
sudo ausearch -m AVC -ts recent

Check for SELinux denials against slapd, database paths, or certificate files.

Avoid these shortcuts in the normal restore procedure:

text
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


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.


Frequently Asked Questions

1. What should be included in a complete OpenLDAP backup?

Export cn=config with slapcat -n 0, export each olcMdbConfig database once with slapcat -g -b using one of its suffixes, archive TLS keys and service files outside LDIF, and keep a manifest with database mapping, entry counts, package versions, and checksums. OpenLDAP supports multiple suffix values on a database definition, while slapcat -b uses the supplied suffix to select the corresponding database.

2. How do I back up cn=config?

Run slapcat -F /etc/openldap/slapd.d -n 0 -l cn-config.ldif; the output is the complete dynamic configuration tree intended for slapadd -n 0 recovery.

3. How do I find and back up all OpenLDAP MDB databases?

Query cn=config for objectClass=olcMdbConfig, iterate once per MDB database entry, record its olcDatabase DN and suffix list, then run slapcat -g -b with one primary suffix per physical database.

4. Do I need to back up cn=accesslog?

Back up cn=accesslog when it is used for delta-syncrepl or auditing. For delta-syncrepl recovery, restore the main database and accesslog from the same coordinated backup. If a matching accesslog backup is unavailable, restore the main database without the stale accesslog, recreate an empty accesslog database, and validate or reseed replication.

5. Can slapcat run while slapd is active?

Yes for MDB backends on supported OpenLDAP versions; the dump is a valid database snapshot, though multi-step application workflows may not represent one coordinated business state without a quiesce.

6. What is the difference between slapcat and ldapsearch for backups?

slapcat produces LDIF with operational attributes for slapadd recovery; ldapsearch is an online query tool and is not a substitute for full offline database restore.

7. What is the difference between slapcat and mdb_copy?

slapcat writes portable logical LDIF; mdb_copy copies the native LMDB environment faster but has stricter compatibility requirements between hosts and versions.

8. Does a cn=config backup include OpenLDAP users and groups?

No. cn=config holds schemas, databases, ACLs, and module settings; user and group entries live in separate MDB databases that must each be exported with slapcat -g -b.

9. Does slapcat preserve password hashes and operational attributes?

Yes. slapcat output includes stored operational attributes such as entryUUID and entryCSN plus attribute values like userPassword hashes needed for authentication after slapadd.

10. Should I restore cn=config before directory data?

Yes for a full disaster recovery. Import cn=config first so database paths, schemas, indexes, and ACLs exist before you restore each MDB database into its empty directory.

11. Why must the MDB database directory be empty before slapadd?

slapadd creates a fresh MDB environment; importing into a directory that already contains data commonly fails with MDB_KEYEXIST or duplicate-entry errors.

12. Why does slapadd report MDB_KEYEXIST?

The target olcDbDirectory still contains an existing MDB database; rename the damaged directory, create a new empty directory owned by ldap:ldap, then rerun slapadd.

13. Should I use slapadd -q, -c, or -w during restore?

Avoid -c because it continues after errors and can leave an incomplete import. Avoid -q for routine recovery because it performs fewer consistency checks, and an error or interruption can leave the resulting database unusable. Use -w only when intentionally loading a fresh seed provider because it overwrites Syncrepl context information with the greatest CSN found.

14. How often should OpenLDAP backups run?

Match backup frequency to your recovery point objective; many sites run online logical backups daily, quiesced backups before major changes, and a full restore drill quarterly.

15. How long should OpenLDAP backups be retained?

Keep local copies for roughly two to four weeks, retain remote copies longer, and never delete the last known-good backup merely because newer scheduled jobs failed.

16. How do I schedule OpenLDAP backups with a systemd timer?

Install a oneshot openldap-backup.service that runs the backup script under flock, then enable openldap-backup.timer with OnCalendar, Persistent=true, and a short RandomizedDelaySec.

17. How do I verify that an OpenLDAP backup can be restored?

Restore the backup on an isolated host, run slaptest, compare entry counts to the manifest, test searches and password binds, and document the drill results.

18. Can replication replace OpenLDAP backups?

No. Replication protects against single-node failure but not accidental mass deletes, ransomware, or long-outage scenarios where every replica is affected; keep independent backups.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive …