Configure OpenLDAP Multi-Provider Replication on RHEL-Based Linux (Multi-Master)

This guide configures two writable OpenLDAP providers using multi-provider replication, traditionally called multi-master replication. The procedure targets current RHEL-family 9 and 10 systems running compatible OpenLDAP 2.6 packages, including RHEL, Rocky Linux, AlmaLinux, Oracle Linux, and CentOS Stream. EPEL currently provides OpenLDAP 2.6 server packages for both EL9 and EL10 branches, although exact builds differ.

Tested on: Rocky Linux 10 with OpenLDAP 2.6.10 packages from EPEL on ldap-server.example.com and ldap-client.example.com.

The lab uses these existing hostnames:

  • ldap-server.example.com — provider 1
  • ldap-client.example.com — provider 2

Although the second host is named ldap-client, it runs openldap-servers and slapd in this article. Both hosts operate as writable OpenLDAP providers.

This guide builds:

  • Two writable MDB databases
  • Unique OpenLDAP server IDs
  • A dedicated replication identity
  • A local accesslog database on each provider
  • Delta-syncrepl in both directions
  • Strict StartTLS peer verification
  • olcMultiProvider
  • Controlled MirrorMode guidance
  • Write, recovery, and conflict verification

Complete these lessons first:

Read the provider-consumer replication guide first if you need a conceptual introduction to Syncrepl. It is not necessary to build that complete topology before starting this guide.

Each provider needs matching schemas, indexes, and ACL layout on both sides. Use OpenLDAP cn=config and MDB explained when you compare olcServerID, overlay, and database settings, and take a baseline export with OpenLDAP backup and restore before you change replication topology on a live directory.

If ldap-client.example.com is already the consumer from the earlier one-way guide, reuse the existing replication account and provider syncprov overlay. Replace its standard olcSyncrepl agreement with the delta-syncrepl definition in this guide and remove olcUpdateRef before making the database writable. Inspect the current olcSyncrepl and olcUpdateRef values after you discover $MDB_DN in the backup section below—do not run that search before $MDB_DN is set.

Do not add a second agreement to the same peer while leaving the previous one-way agreement active.

WARNING
OpenLDAP multi-provider replication is not a write load balancer or a distributed transactional database. Allowing both providers to accept conflicting writes during a network partition can produce directory conflicts that require manual investigation.

Understand Multi-Provider and MirrorMode Replication

OpenLDAP now uses multi-provider replication. Older documentation and search queries commonly call the same general topology multi-master replication.

text
Bidirectional
              delta-syncrepl over TLS
             <----------------------->
ldap-server.example.com          ldap-client.example.com
Provider 1                       Provider 2
Read/write                       Read/write
Server ID 1                      Server ID 2
Local accesslog                  Local accesslog
Topology Writable nodes Replication direction Best use
Provider-consumer One One-way Read scaling and standby copies
Multi-provider Two or more Bidirectional Multiple writable sites
MirrorMode Two capable providers, one active writer Bidirectional Controlled write high availability

Every multi-provider node acts as both provider and consumer. Multi-provider improves write availability, not write throughput. Each provider must process changes received from peers.

MirrorMode uses the same bidirectional replication foundation. An external VIP, proxy, or load balancer sends writes to only one provider at a time. MirrorMode changes how clients send writes, not how Syncrepl itself functions.

This guide replicates the directory data database. cn=config replication is outside this article. Equivalent schemas, ACLs, indexes, and overlays are configured independently on both nodes.

OpenLDAP renamed olcMirrorMode to olcMultiProvider. The old name remains an alias but should not be used alongside the new attribute.


Prepare the RHEL-Family Replication Lab

Setting Provider 1 Provider 2
Hostname ldap-server.example.com ldap-client.example.com
IP address 192.168.56.108 192.168.56.109
OpenLDAP role Writable provider Writable provider
Server ID 1 2
Base DN dc=example,dc=com dc=example,dc=com
Backend MDB MDB
Replication peer ldap-client.example.com ldap-server.example.com
Transport StartTLS StartTLS

Both nodes need compatible OpenLDAP 2.6 packages, the same directory suffix, the same standard and custom schemas, equivalent ACL intent, equivalent database indexes, equivalent overlays, working hostname resolution, synchronized clocks, TLS certificates containing the hostname used in the LDAP URI, trust in the same CA, and TCP port 389 permitted between nodes.

Add lab name resolution where DNS is unavailable:

text
192.168.56.108 ldap-server.example.com ldap-server
192.168.56.109 ldap-client.example.com ldap-client

Confirm the hostname on the provider:

bash
hostname -f

Sample output:

output
ldap-server.example.com

Verify name resolution on both hosts:

bash
getent hosts ldap-server.example.com ldap-client.example.com

On the provider you should see the peer address:

output
192.168.56.109  ldap-client.example.com ldap-client

Check matching packages on each node:

bash
rpm -q openldap openldap-servers openldap-clients

Sample output:

output
openldap-2.6.10-1.el10.x86_64
openldap-servers-2.6.10-1.el10_2.x86_64
openldap-clients-2.6.10-1.el10.x86_64

Confirm slapd is active before changing replication:

bash
systemctl is-active slapd

Sample output:

output
active

Enable time synchronization on both nodes:

bash
sudo systemctl enable --now chronyd
bash
chronyc tracking
bash
timedatectl show -p NTPSynchronized

Sample output:

output
NTPSynchronized=yes

Test TLS from ldap-server.example.com to ldap-client.example.com. The -verify_hostname option checks the certificate identity against its DNS SAN or subject name:

bash
openssl s_client \
  -starttls ldap \
  -connect ldap-client.example.com:389 \
  -servername ldap-client.example.com \
  -verify_hostname ldap-client.example.com \
  -CAfile /etc/openldap/certs/example-ldap-ca.crt \
  -verify_return_error \
  -brief </dev/null

Sample output:

output
Verification: OK
Verified peername: ldap-client.example.com
DONE

Test TLS from ldap-client.example.com to ldap-server.example.com:

bash
openssl s_client \
  -starttls ldap \
  -connect ldap-server.example.com:389 \
  -servername ldap-server.example.com \
  -verify_hostname ldap-server.example.com \
  -CAfile /etc/openldap/certs/example-ldap-ca.crt \
  -verify_return_error \
  -brief </dev/null

Sample output:

output
Verification: OK
Verified peername: ldap-server.example.com
DONE

The TLS certificate SAN must include ldap-server.example.com or ldap-client.example.com when those FQDNs are used in replication URIs.


Offline imports in this section use the slapadd command.

Back Up and Compare Both OpenLDAP Providers

Before changing replication, export the configuration and data on both nodes so you can roll back if a modify step goes wrong.

Create a protected backup directory owned by root with mode 0700:

bash
sudo install -d -m 0700 /root/openldap-before-multiprovider

Export cn=config, which holds server IDs, syncrepl definitions, and overlay settings:

bash
sudo slapcat -n 0 -l /root/openldap-before-multiprovider/cn-config.ldif

Export the live directory data under the replicated suffix:

bash
sudo slapcat -b "dc=example,dc=com" -l /root/openldap-before-multiprovider/example-com.ldif

Generate checksums so you can confirm the export files did not change before you alter replication:

bash
sudo sha256sum /root/openldap-before-multiprovider/*.ldif

Record the three hash lines somewhere safe—you will compare against them only if you need to prove the pre-change export stayed intact.

Discover the MDB database DN dynamically on both servers before you edit any LDIF that references the main database:

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

Store the main data database DN in a shell variable for later commands:

bash
MDB_DN=$(
  sudo ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// \
    -b cn=config '(&(objectClass=olcMdbConfig)(olcSuffix=dc=example,dc=com))' dn |
  awk '/^dn: / {sub(/^dn: /, ""); print; exit}'
)

Verify the variable:

bash
printf 'Main MDB database: %s\n' "$MDB_DN"

Sample output:

output
Main MDB database: olcDatabase={2}mdb,cn=config

Confirm the variable is set before you use it in later commands:

bash
if [[ -z "$MDB_DN" ]]; then
  echo "Main MDB database was not found." >&2
  exit 1
fi

When converting from the provider-consumer guide, inspect the peer node's current replication settings:

bash
sudo ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// \
  -b "$MDB_DN" -s base \
  olcSyncrepl olcUpdateRef olcMultiProvider

Sample output when ldap-client.example.com still has the one-way configuration:

output
olcSyncrepl: {0}rid=001 provider=ldap://ldap-server.example.com ...
olcUpdateRef: ldaps://ldap-server.example.com

Compare both nodes for:

  • olcSuffix
  • Loaded modules
  • Schema DNs and custom schemas
  • olcAccess
  • olcDbIndex
  • Existing overlays
  • Existing olcServerID
  • Existing olcSyncrepl
  • Existing accesslog databases
  • Entry counts

Choose one authoritative initial dataset:

output
Authoritative initial provider: ldap-server.example.com

Do not begin bidirectional replication while both nodes contain independently modified copies of the same directory.


Assign Unique OpenLDAP Server IDs

Identifier Purpose
olcServerID Identifies the provider that generated a change sequence number
Syncrepl rid Identifies one replication agreement locally

Because this guide does not replicate cn=config, assign a local numeric ID on each node. OpenLDAP warns that mismatched replication and listener URLs can cause a server to replicate from itself when URI-based identity selection is used.

On ldap-server.example.com:

bash
vi configure-server-id.ldif
ldif
dn: cn=config
changetype: modify
replace: olcServerID
olcServerID: 1
bash
sudo ldapmodify -Q -Y EXTERNAL -H ldapi:/// -f configure-server-id.ldif

On ldap-client.example.com, use server ID 2:

ldif
dn: cn=config
changetype: modify
replace: olcServerID
olcServerID: 2

Verify on each node:

bash
sudo ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b cn=config -s base olcServerID

On ldap-server.example.com:

output
olcServerID: 1

On ldap-client.example.com:

output
olcServerID: 2

Each writable provider requires a unique non-zero ID. Do not configure both nodes with the same server ID or change IDs casually after replication begins.

If slapadd must generate new CSNs for a multi-provider database, use an appropriate valid SID with slapadd -S rather than allowing generated CSNs to use SID 000.


Create the Replication Account and ACL

Skip this section on the authoritative provider when uid=replicator,ou=service-accounts,dc=example,dc=com already exists from the provider-consumer guide.

Generate a password hash on the authoritative provider:

bash
slappasswd

Create the account LDIF:

bash
umask 077
vi replication-account.ldif
ldif
dn: ou=service-accounts,dc=example,dc=com
objectClass: top
objectClass: organizationalUnit
ou: service-accounts

dn: uid=replicator,ou=service-accounts,dc=example,dc=com
objectClass: top
objectClass: account
objectClass: simpleSecurityObject
uid: replicator
description: OpenLDAP multi-provider replication account
userPassword: {SSHA}REPLACE_WITH_HASH

Add the entry to the authoritative provider:

bash
ldapadd -x -ZZ \
  -H ldap://ldap-server.example.com \
  -D "cn=admin,dc=example,dc=com" -W \
  -f replication-account.ldif

The account replicates to the second provider during initial synchronization.

Apply the main-database replication ACL and unlimited search limits independently on both providers. These settings belong to cn=config and do not replicate with directory entries. Before enabling the reverse agreement, each node must allow the replicator to read its main database and its local cn=accesslog database.

Inspect current ACL and limits on the main MDB database:

bash
sudo ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b "$MDB_DN" -s base olcAccess olcLimits

Insert replication read access before broader deny rules. Do not replace unrelated olcAccess values:

bash
vi replication-acl.ldif
ldif
dn: REPLACE_WITH_MDB_DN
changetype: modify
add: olcAccess
olcAccess: {0}to *
  by dn.exact="uid=replicator,ou=service-accounts,dc=example,dc=com" read
  by * break

Apply on ldap-server.example.com:

bash
sudo ldapmodify -Q -Y EXTERNAL -H ldapi:/// -f replication-acl.ldif

Apply the same file on ldap-client.example.com after its main database exists.

Add unlimited search limits for the replicator on the main database:

bash
vi replication-limits.ldif
ldif
dn: REPLACE_WITH_MDB_DN
changetype: modify
add: olcLimits
olcLimits: dn.exact="uid=replicator,ou=service-accounts,dc=example,dc=com" time.soft=unlimited time.hard=unlimited size.soft=unlimited size.hard=unlimited
bash
sudo ldapmodify -Q -Y EXTERNAL -H ldapi:/// -f replication-limits.ldif

Repeat on the peer provider. Use add: when no olcLimits entry exists for the replicator; if one already exists, preserve and update that value instead of creating a duplicate rule.

Protect cn=config backups because replication credentials are stored there. Delta-syncrepl also requires read access to the local cn=accesslog database—configure that ACL after the accesslog database is created in the next section.

Test from ldap-client.example.com:

bash
ldapsearch -x -ZZ \
  -H ldap://ldap-server.example.com \
  -D "uid=replicator,ou=service-accounts,dc=example,dc=com" \
  -W \
  -b "dc=example,dc=com" \
  -LLL \
  "(objectClass=*)" \
  dn entryUUID entryCSN

A successful search lists entries with operational attributes—evidence the ACL permits replication reads.


Configure accesslog and syncprov on Both Providers

Component Purpose
Main MDB database Stores users, groups, and directory data
accesslog overlay Records successful write operations
cn=accesslog MDB database Stores the persistent change log
syncprov on main database Exposes synchronization state
syncprov on accesslog Supports delta synchronization

Inspect loaded modules:

bash
sudo ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b cn=config '(objectClass=olcModuleList)' dn olcModulePath olcModuleLoad

Inspect existing overlays:

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

Structure the configuration in separate, testable LDIF files on each node:

text
01-load-replication-modules.ldif
02-create-accesslog-database.ldif
03-add-accesslog-syncprov.ldif
04-add-main-syncprov.ldif
05-add-accesslog-overlay.ldif

Create a local accesslog database

Prepare the directory with an OpenLDAP database SELinux context:

bash
sudo dnf install -y policycoreutils-python-utils
bash
sudo install -d -o ldap -g ldap -m 0700 /var/lib/ldap-accesslog
bash
sudo semanage fcontext -a -t slapd_db_t '/var/lib/ldap-accesslog(/.*)?'
bash
sudo restorecon -Rv /var/lib/ldap-accesslog
bash
ls -ldZ /var/lib/ldap-accesslog

Create the accesslog database without a hardcoded index (02-create-accesslog-database.ldif):

ldif
dn: olcDatabase=mdb,cn=config
changetype: add
objectClass: olcDatabaseConfig
objectClass: olcMdbConfig
olcDatabase: mdb
olcSuffix: cn=accesslog
olcRootDN: cn=accesslog
olcDbDirectory: /var/lib/ldap-accesslog
olcDbMaxSize: 1073741824
olcDbIndex: default eq
olcDbIndex: entryCSN,entryUUID,objectClass,reqEnd,reqResult,reqStart,reqDN eq

The MDB default size is only 10 MB—too small for a persistent accesslog in most deployments. Size olcDbMaxSize according to write volume and retention; 1 GiB is a reasonable lab example, not a universal production value.

Apply it:

bash
sudo ldapadd -Q -Y EXTERNAL -H ldapi:/// \
  -f 02-create-accesslog-database.ldif

Discover the assigned DN:

bash
ACCESSLOG_DN=$(
  sudo ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// \
    -b cn=config \
    '(&(objectClass=olcMdbConfig)(olcSuffix=cn=accesslog))' dn |
  awk '/^dn: / {sub(/^dn: /, ""); print; exit}'
)
bash
printf 'Accesslog database DN: %s\n' "$ACCESSLOG_DN"

Sample output:

output
Accesslog database DN: olcDatabase={3}mdb,cn=config

Identify the local SASL EXTERNAL identity:

Identity checks in this section use the ldapwhoami command.

bash
sudo ldapwhoami -Q -Y EXTERNAL -H ldapi:///

Sample output:

output
dn:gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth

Create ACL and limits on each accesslog database (accesslog-access.ldif). Replace the EXTERNAL identity if ldapwhoami returns a different value:

ldif
dn: REPLACE_WITH_ACCESSLOG_DN
changetype: modify
add: olcAccess
olcAccess: {0}to *
  by dn.exact="uid=replicator,ou=service-accounts,dc=example,dc=com" read
  by dn.exact="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" manage
  by * none
-
add: olcLimits
olcLimits: dn.exact="uid=replicator,ou=service-accounts,dc=example,dc=com" time.soft=unlimited time.hard=unlimited size.soft=unlimited size.hard=unlimited

Accesslog records can contain modification data and should not be left readable through a broad default ACL.

Discover the module list DN:

bash
MODULE_DN=$(
  sudo ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// \
    -b cn=config '(objectClass=olcModuleList)' dn |
  awk '/^dn: / {sub(/^dn: /, ""); print; exit}'
)

Check which replication modules are already loaded:

bash
sudo ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// \
  -b "$MODULE_DN" -s base olcModuleLoad

Create 01-load-replication-modules.ldif with only the modules that are still missing. When converting from the provider-consumer guide, syncprov.la may already be present—do not add it again in the same modify operation or the transaction can fail before accesslog.la is loaded.

When only accesslog.la is missing:

ldif
dn: REPLACE_WITH_MODULE_DN
changetype: modify
add: olcModuleLoad
olcModuleLoad: accesslog.la

On a fresh installation where both modules are absent, add them in separate modify operations or confirm that neither value exists before combining them:

ldif
dn: REPLACE_WITH_MODULE_DN
changetype: modify
add: olcModuleLoad
olcModuleLoad: syncprov.la
ldif
dn: REPLACE_WITH_MODULE_DN
changetype: modify
add: olcModuleLoad
olcModuleLoad: accesslog.la

Add syncprov on the accesslog database (03-add-accesslog-syncprov.ldif):

ldif
dn: olcOverlay=syncprov,REPLACE_WITH_ACCESSLOG_DN
objectClass: olcOverlayConfig
objectClass: olcSyncProvConfig
olcOverlay: syncprov
olcSpReloadhint: TRUE
olcSpNopresent: TRUE

Configure syncprov and the accesslog overlay on the main database

Add indexes on the main database if they are missing:

ldif
dn: REPLACE_WITH_MDB_DN
changetype: modify
add: olcDbIndex
olcDbIndex: entryCSN eq
-
add: olcDbIndex
olcDbIndex: entryUUID eq

Add syncprov (04-add-main-syncprov.ldif):

ldif
dn: olcOverlay=syncprov,REPLACE_WITH_MDB_DN
objectClass: olcOverlayConfig
objectClass: olcSyncProvConfig
olcOverlay: syncprov
olcSpCheckpoint: 100 10
olcSpSessionlog: 1000

Example retention planning:

Expected maximum peer outage Minimum useful log retention
Several hours At least one day
Several days Longer than the planned outage window
Remote or unreliable site Add operational margin

Configure the accesslog overlay (05-add-accesslog-overlay.ldif):

ldif
dn: olcOverlay=accesslog,REPLACE_WITH_MDB_DN
objectClass: olcOverlayConfig
objectClass: olcAccessLogConfig
olcOverlay: accesslog
olcAccessLogDB: cn=accesslog
olcAccessLogOps: writes
olcAccessLogSuccess: TRUE
olcAccessLogPurge: 07+00:00 01+00:00

That retains seven days of history and checks for purgeable records once per day. Production retention must exceed the longest expected peer outage with operational margin.

Each provider has its own local accesslog. Do not replicate cn=accesslog as normal directory content. When the needed delta history is no longer available, OpenLDAP falls back to conventional Syncrepl to refresh full entries before returning to delta mode.

Apply the LDIF files on both nodes in this order. Load modules first so syncprov and accesslog can be added as overlays:

bash
sudo ldapmodify -Q -Y EXTERNAL -H ldapi:/// \
  -f 01-load-replication-modules.ldif

Create the local cn=accesslog database on that node:

bash
sudo ldapadd -Q -Y EXTERNAL -H ldapi:/// \
  -f 02-create-accesslog-database.ldif

Discover ACCESSLOG_DN, then restrict who can read the change log:

bash
sudo ldapmodify -Q -Y EXTERNAL -H ldapi:/// \
  -f accesslog-access.ldif

Add syncprov on the accesslog database so delta consumers can synchronize against it:

bash
sudo ldapadd -Q -Y EXTERNAL -H ldapi:/// \
  -f 03-add-accesslog-syncprov.ldif

Add syncprov on the main database unless the provider-consumer guide already placed it there:

bash
sudo ldapadd -Q -Y EXTERNAL -H ldapi:/// \
  -f 04-add-main-syncprov.ldif

Finally, attach the accesslog overlay to the main database so successful writes are logged:

bash
sudo ldapadd -Q -Y EXTERNAL -H ldapi:/// \
  -f 05-add-accesslog-overlay.ldif

If syncprov already exists on the primary database from the provider-consumer guide, verify it and skip file 04-add-main-syncprov.ldif rather than adding a duplicate. Instantiate syncprov on the main database before the accesslog overlay in a delta-syncrepl configuration.

Verify all overlays:

bash
sudo ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b cn=config '(objectClass=olcOverlayConfig)' dn olcOverlay olcSpCheckpoint olcSpNoPresent olcSpReloadHint

Apply the same stack independently on ldap-server.example.com and ldap-client.example.com.

Back up each delta-syncrepl provider

After the accesslog database exists on a provider, export the main database, its local accesslog, and cn=config together on that node. OpenLDAP requires the primary database and changelog database to be backed up and restored together for delta-syncrepl providers.

Create a root-only backup directory:

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

Export the directory data that applications query:

bash
sudo slapcat -b "dc=example,dc=com" \
  -l /root/openldap-backup/example-com.ldif

Export the local change log that delta-syncrepl reads:

bash
sudo slapcat -b "cn=accesslog" \
  -l /root/openldap-backup/accesslog.ldif

Export the server configuration, including replication credentials in olcSyncrepl:

bash
sudo slapcat -n 0 \
  -l /root/openldap-backup/cn-config.ldif

Generate checksums for all three exports:

bash
sudo sha256sum /root/openldap-backup/*.ldif

Store the checksum output with the LDIF files. Repeat the same four steps on the peer provider after its accesslog stack is in place.


Configure Bidirectional Delta-Syncrepl

Configure each provider to consume from the other after the accesslog stack is in place on both nodes.

On ldap-server.example.com the remote peer is ldap-client.example.com (rid=102). On ldap-client.example.com the remote peer is ldap-server.example.com (rid=101).

OpenLDAP requires the database to already be an effective Syncrepl consumer before olcMultiProvider can be enabled. Do not add olcMultiProvider in the initial consumer LDIF.

Phase 1 — initial sync on ldap-client.example.com

On ldap-client.example.com, remove the old one-way agreement if present. Verify how many values exist before you delete anything:

bash
sudo ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// \
  -b "$MDB_DN" -s base olcSyncrepl olcUpdateRef

When only one agreement exists—the usual case after the provider-consumer guide—delete the complete attributes without specifying a partial olcSyncrepl value:

bash
vi remove-oneway-syncrepl.ldif
ldif
dn: REPLACE_WITH_MDB_DN
changetype: modify
delete: olcSyncrepl
-
delete: olcUpdateRef

When multiple Syncrepl agreements exist, do not delete the entire attribute. Copy and delete the exact full value of only the obsolete agreement.

bash
sudo ldapmodify -Q -Y EXTERNAL -H ldapi:/// -f remove-oneway-syncrepl.ldif

Create the initial delta-syncrepl LDIF without olcMultiProvider:

bash
umask 077
vi delta-syncrepl-initial.ldif

On ldap-client.example.com:

ldif
dn: REPLACE_WITH_MDB_DN
changetype: modify
add: olcSyncrepl
olcSyncrepl: rid=101
  provider="ldap://ldap-server.example.com"
  bindmethod=simple
  binddn="uid=replicator,ou=service-accounts,dc=example,dc=com"
  credentials="REPLACE_WITH_PASSWORD"
  searchbase="dc=example,dc=com"
  type=refreshAndPersist
  retry="5 5 60 +"
  timeout=3
  schemachecking=on
  starttls=critical
  tls_cacert="/etc/openldap/certs/example-ldap-ca.crt"
  tls_reqcert=demand
  tls_reqsan=demand
  logbase="cn=accesslog"
  logfilter="(&(objectClass=auditWriteObject)(reqResult=0))"
  syncdata=accesslog
bash
sudo ldapmodify -Q -Y EXTERNAL -H ldapi:/// -f delta-syncrepl-initial.ldif

Wait until entries, entryUUID values, and the complete contextCSN set match the authoritative provider.

Phase 2 — enable bidirectional multi-provider

Briefly stop application writes, then add the reverse agreement on ldap-server.example.com and enable olcMultiProvider in the same modify transaction:

bash
vi delta-syncrepl-reverse.ldif
ldif
dn: REPLACE_WITH_MDB_DN
changetype: modify
add: olcSyncrepl
olcSyncrepl: rid=102
  provider="ldap://ldap-client.example.com"
  bindmethod=simple
  binddn="uid=replicator,ou=service-accounts,dc=example,dc=com"
  credentials="REPLACE_WITH_PASSWORD"
  searchbase="dc=example,dc=com"
  type=refreshAndPersist
  retry="5 5 60 +"
  timeout=3
  schemachecking=on
  starttls=critical
  tls_cacert="/etc/openldap/certs/example-ldap-ca.crt"
  tls_reqcert=demand
  tls_reqsan=demand
  logbase="cn=accesslog"
  logfilter="(&(objectClass=auditWriteObject)(reqResult=0))"
  syncdata=accesslog
-
add: olcMultiProvider
olcMultiProvider: TRUE
bash
sudo ldapmodify -Q -Y EXTERNAL -H ldapi:/// -f delta-syncrepl-reverse.ldif

Enable multi-provider on ldap-client.example.com in a separate file:

bash
vi enable-multiprovider.ldif
ldif
dn: REPLACE_WITH_MDB_DN
changetype: modify
add: olcMultiProvider
olcMultiProvider: TRUE
bash
sudo ldapmodify -Q -Y EXTERNAL -H ldapi:/// -f enable-multiprovider.ldif

Restart and validate one node at a time. Resume application writes only after both directions are healthy.

Option Purpose
rid Unique local replication-agreement identifier
provider Remote provider URI
searchbase Replicated directory suffix
refreshAndPersist Initial refresh followed by persistent replication
starttls=critical Prevents fallback to an unencrypted connection
logbase Selects the accesslog database
logfilter Selects successful write records
syncdata=accesslog Enables delta-syncrepl
retry Controls reconnection behavior
olcMultiProvider Marks the database as a writable multi-provider node

Use olcMultiProvider only. Do not add olcMirrorMode alongside it.

Verify on each node:

bash
sudo ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b "$MDB_DN" -s base olcSyncrepl olcMultiProvider

Restart one node at a time:

bash
sudo systemctl restart slapd
bash
systemctl is-active slapd

After a provider restart, inspect the unit with journalctl when replication or TLS errors persist:

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

Initialize and Verify Multi-Provider Replication

Keep ldap-server.example.com authoritative during initialization.

Compare entry counts

Count DNs on the authoritative provider first:

bash
ldapsearch -x -ZZ -H ldap://ldap-server.example.com -D "cn=admin,dc=example,dc=com" -W -b "dc=example,dc=com" -LLL "(objectClass=*)" dn | grep -c '^dn:'

Run the same count on the peer:

bash
ldapsearch -x -ZZ -H ldap://ldap-client.example.com -D "cn=admin,dc=example,dc=com" -W -b "dc=example,dc=com" -LLL "(objectClass=*)" dn | grep -c '^dn:'

Equal counts alone do not prove synchronization—the peer may still be missing entries or holding stale data.

Compare known entries

Request dn, entryUUID, entryCSN, and modifyTimestamp for a known entry on both providers. The same logical entry should share the same entryUUID.

Compare contextCSN

Query the suffix entry on the provider:

bash
ldapsearch -x -ZZ -H ldap://ldap-server.example.com -D "cn=admin,dc=example,dc=com" -W -b "dc=example,dc=com" -s base -LLL contextCSN

Repeat against ldap-client.example.com. Multi-provider contextCSN can contain CSNs for multiple server IDs. Compare the complete set rather than assuming a single scalar value.

Test writes from ldap-server.example.com

Create mmtest-server.ldif:

ldif
dn: uid=mmtest-server,ou=people,dc=example,dc=com
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: top
cn: MM Test Server
sn: Test
uid: mmtest-server
uidNumber: 10060
gidNumber: 10060
homeDirectory: /home/mmtest-server

Add, modify, rename with -r, and delete the entry on ldap-server.example.com. Verify each operation on ldap-client.example.com.

Test writes from ldap-client.example.com

Repeat with uid=mmtest-client,ou=people,dc=example,dc=com on ldap-client.example.com and verify on ldap-server.example.com.

Inspect the accesslog

Query cn=accesslog locally on the provider that received the original write:

bash
sudo ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b cn=accesslog '(objectClass=auditWriteObject)' reqStart reqType reqDN reqResult reqAuthzID | head -20

The two providers maintain separate local accesslogs. Their contents are not expected to be identical.


Configure and Test MirrorMode

output
Applications
     |
     v
Write VIP or LDAP proxy
     |
     +---- Active writer: ldap-server.example.com
     |
     `---- Standby writer: ldap-client.example.com

Operating model:

  • Both nodes retain bidirectional replication.
  • Only one endpoint receives application writes.
  • Read traffic may use either healthy provider.
  • During failure, the write endpoint moves to the surviving provider.
  • A recovered provider must catch up before becoming eligible.
  • Do not send writes to both nodes using round-robin DNS.

A VIP, hardware load balancer, HAProxy, lloadd, or another controlled frontend may manage the active endpoint. Keep complete proxy or Keepalived configuration outside this article.

Test controlled failover

  1. Confirm writes currently reach ldap-server.example.com.
  2. Stop slapd on ldap-server.example.com.
  3. Move the write endpoint to ldap-client.example.com.
  4. Perform a test write on ldap-client.example.com.
  5. Restore ldap-server.example.com.
  6. Verify that ldap-server.example.com catches up.
  7. Compare contextCSN.
  8. Return it to service only after synchronization succeeds.

Test Failure Recovery and Conflict Risks

Test one provider offline

  1. Stop slapd on ldap-client.example.com.
  2. Perform writes on ldap-server.example.com.
  3. Start ldap-client.example.com.
  4. Monitor its journal.
  5. Verify delta-syncrepl catch-up.
  6. Compare entries and CSNs.

Repeat in the opposite direction.

Test accesslog retention behavior

When a provider is offline longer than the retained accesslog history, OpenLDAP falls back to conventional Syncrepl to refresh full entries before returning to delta mode. Inspect olcAccessLogPurge and journalctl to confirm whether catch-up is progressing.

Explain conflict cases

Cover these risks without creating destructive split-brain scenarios on production systems:

  • The same attribute modified on both isolated providers
  • The same DN added independently
  • Rename on one provider while modifying on the other
  • Delete on one provider while modifying on the other
  • Clients writing to both sides of a network partition
  • Duplicate or changed server IDs
  • Large clock differences

Multi-provider replication is eventually synchronized replication. It does not provide distributed transactions or automatic application-level conflict resolution.


Monitor and Troubleshoot Multi-Provider Replication

Monitor contextCSN on both nodes, successful synthetic writes, replication errors in journalctl, peer reachability, TLS certificate expiry, clock synchronization, accesslog size and purge behavior, replication delay, and unexpected entry differences.

Symptom Likely cause Check
database is not a shadow olcMultiProvider added before effective olcSyncrepl Verify olcSyncrepl first; apply both in one modify when possible
Replication works only one way Reverse agreement missing or invalid Compare both database configurations
Both providers contain conflicting initial entries Nodes initialized independently Reinitialize from one authoritative provider
Invalid credentials (49) Wrong replication password or bind DN Test a direct StartTLS bind
Insufficient access (50) Replicator cannot read the DIT or accesslog Review ACL ordering on both databases
TLS negotiation fails CA, SAN, DNS, URI, or certificate problem Test StartTLS in both directions
Accesslog grows indefinitely Purge settings missing or malformed Inspect accesslog overlay attributes
Peer cannot use delta history Outage exceeded accesslog retention OpenLDAP falls back to conventional Syncrepl; check logs and wait for catch-up
Persistent contextCSN difference Replication failing or conflicting writes Compare logs and affected entries
Replication loops or connects to itself Provider URI does not match server identity Review URIs and server-ID mapping
Changes fail after restart Duplicate server IDs, RIDs, or invalid config Inspect cn=config
Both providers accept writes while isolated Split-brain topology Stop writes on one side or use controlled MirrorMode

Diagnostic commands:

Start with recent slapd messages when replication stops moving:

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

Read server IDs, syncrepl agreements, and multi-provider flags in one query:

bash
sudo ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b cn=config '(|(olcServerID=*)(olcSyncrepl=*)(olcMultiProvider=TRUE))' dn olcServerID olcSyncrepl olcMultiProvider

Confirm the expected overlays are loaded on each provider:

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

Check that slapd is listening on the LDAP ports you configured:

bash
sudo ss -ltnp | grep slapd

Confirm clock synchronization when CSNs look out of order:

bash
chronyc tracking

References


Summary

You configured two writable OpenLDAP providers with unique server IDs, a dedicated replication identity, a local accesslog database on each server, syncprov, bidirectional delta-syncrepl, strict StartTLS, olcMultiProvider, write verification in both directions, accesslog-based recovery after downtime, controlled MirrorMode behavior, and split-brain precautions.


Frequently Asked Questions

1. What is the difference between OpenLDAP multi-master and multi-provider replication?

They describe the same writable-on-both-sides topology. Multi-provider is current OpenLDAP terminology. Multi-master is the older name still common in search queries and legacy documentation.

2. Is OpenLDAP multi-provider replication a load balancer?

No. Multi-provider improves write availability by allowing more than one writable server, but it does not distribute or balance write load across nodes. Each provider still processes changes received from peers.

3. What is the difference between multi-provider replication and MirrorMode?

Both use bidirectional Syncrepl. Multi-provider allows writes on every connected provider. MirrorMode keeps the same replication foundation but expects an external VIP, proxy, or load balancer to send application writes to only one active endpoint at a time.

4. Why does every OpenLDAP provider need a unique server ID?

olcServerID identifies which provider generated a change sequence number. Duplicate or inconsistent server IDs produce CSN and replication errors after restarts or imports.

5. What is the difference between olcServerID and Syncrepl rid?

olcServerID is a global provider identity used in CSNs. Syncrepl rid is a local identifier for one replication agreement on one database. Each node needs unique server IDs and unique rids for each peer agreement.

6. Why does delta-syncrepl need an accesslog database?

Delta-syncrepl reads successful write records from a local cn=accesslog database instead of replicating whole entries for every change. Each provider maintains its own accesslog; it is not replicated as normal directory content.

7. How do I verify that both OpenLDAP providers are synchronized?

Compare entryUUID on known entries, inspect the full contextCSN set on the suffix entry, test writes from each provider, and review slapd logs. Equal entry counts alone do not prove synchronization.

8. What happens if both providers accept writes during a network partition?

The directory can diverge with conflicting attribute values, duplicate DNs, or inconsistent renames. Multi-provider replication is eventually synchronized replication, not distributed transactions. Use controlled MirrorMode or stop writes on one side until the partition heals.

9. Why do I get database is not a shadow?

olcMultiProvider was enabled before an effective olcSyncrepl agreement existed on that database, or the syncrepl definition is misconfigured. Add working syncrepl first, wait for initial synchronization, then enable olcMultiProvider on the reverse provider in the same modify transaction as the second agreement.

10. Can cn=config also be replicated?

Not in this guide. This walkthrough replicates the directory data database only. Equivalent schemas, ACLs, indexes, and overlays are configured independently on both nodes.
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 experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)