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.comandldap-client.example.com.
The lab uses these existing hostnames:
ldap-server.example.com— provider 1ldap-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.
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.
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:
192.168.56.108 ldap-server.example.com ldap-server
192.168.56.109 ldap-client.example.com ldap-clientConfirm the hostname on the provider:
hostname -fSample output:
ldap-server.example.comVerify name resolution on both hosts:
getent hosts ldap-server.example.com ldap-client.example.comOn the provider you should see the peer address:
192.168.56.109 ldap-client.example.com ldap-clientCheck matching packages on each node:
rpm -q openldap openldap-servers openldap-clientsSample 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_64Confirm slapd is active before changing replication:
systemctl is-active slapdSample output:
activeEnable time synchronization on both nodes:
sudo systemctl enable --now chronydchronyc trackingtimedatectl show -p NTPSynchronizedSample output:
NTPSynchronized=yesTest 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:
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/nullSample output:
Verification: OK
Verified peername: ldap-client.example.com
DONETest TLS from ldap-client.example.com to ldap-server.example.com:
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/nullSample output:
Verification: OK
Verified peername: ldap-server.example.com
DONEThe 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:
sudo install -d -m 0700 /root/openldap-before-multiproviderExport cn=config, which holds server IDs, syncrepl definitions, and overlay settings:
sudo slapcat -n 0 -l /root/openldap-before-multiprovider/cn-config.ldifExport the live directory data under the replicated suffix:
sudo slapcat -b "dc=example,dc=com" -l /root/openldap-before-multiprovider/example-com.ldifGenerate checksums so you can confirm the export files did not change before you alter replication:
sudo sha256sum /root/openldap-before-multiprovider/*.ldifRecord 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:
sudo ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b cn=config '(objectClass=olcMdbConfig)' dn olcSuffix olcDbDirectory olcDbIndexStore the main data database DN in a shell variable for later commands:
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:
printf 'Main MDB database: %s\n' "$MDB_DN"Sample output:
Main MDB database: olcDatabase={2}mdb,cn=configConfirm the variable is set before you use it in later commands:
if [[ -z "$MDB_DN" ]]; then
echo "Main MDB database was not found." >&2
exit 1
fiWhen converting from the provider-consumer guide, inspect the peer node's current replication settings:
sudo ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// \
-b "$MDB_DN" -s base \
olcSyncrepl olcUpdateRef olcMultiProviderSample output when ldap-client.example.com still has the one-way configuration:
olcSyncrepl: {0}rid=001 provider=ldap://ldap-server.example.com ...
olcUpdateRef: ldaps://ldap-server.example.comCompare both nodes for:
olcSuffix- Loaded modules
- Schema DNs and custom schemas
olcAccessolcDbIndex- Existing overlays
- Existing
olcServerID - Existing
olcSyncrepl - Existing accesslog databases
- Entry counts
Choose one authoritative initial dataset:
Authoritative initial provider: ldap-server.example.comDo 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:
vi configure-server-id.ldifdn: cn=config
changetype: modify
replace: olcServerID
olcServerID: 1sudo ldapmodify -Q -Y EXTERNAL -H ldapi:/// -f configure-server-id.ldifOn ldap-client.example.com, use server ID 2:
dn: cn=config
changetype: modify
replace: olcServerID
olcServerID: 2Verify on each node:
sudo ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b cn=config -s base olcServerIDOn ldap-server.example.com:
olcServerID: 1On ldap-client.example.com:
olcServerID: 2Each 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:
slappasswdCreate the account LDIF:
umask 077
vi replication-account.ldifdn: 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_HASHAdd the entry to the authoritative provider:
ldapadd -x -ZZ \
-H ldap://ldap-server.example.com \
-D "cn=admin,dc=example,dc=com" -W \
-f replication-account.ldifThe 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:
sudo ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b "$MDB_DN" -s base olcAccess olcLimitsInsert replication read access before broader deny rules. Do not replace unrelated olcAccess values:
vi replication-acl.ldifdn: 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 * breakApply on ldap-server.example.com:
sudo ldapmodify -Q -Y EXTERNAL -H ldapi:/// -f replication-acl.ldifApply the same file on ldap-client.example.com after its main database exists.
Add unlimited search limits for the replicator on the main database:
vi replication-limits.ldifdn: 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=unlimitedsudo ldapmodify -Q -Y EXTERNAL -H ldapi:/// -f replication-limits.ldifRepeat 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:
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 entryCSNA 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:
sudo ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b cn=config '(objectClass=olcModuleList)' dn olcModulePath olcModuleLoadInspect existing overlays:
sudo ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b cn=config '(objectClass=olcOverlayConfig)' dn olcOverlayStructure the configuration in separate, testable LDIF files on each node:
01-load-replication-modules.ldif
02-create-accesslog-database.ldif
03-add-accesslog-syncprov.ldif
04-add-main-syncprov.ldif
05-add-accesslog-overlay.ldifCreate a local accesslog database
Prepare the directory with an OpenLDAP database SELinux context:
sudo dnf install -y policycoreutils-python-utilssudo install -d -o ldap -g ldap -m 0700 /var/lib/ldap-accesslogsudo semanage fcontext -a -t slapd_db_t '/var/lib/ldap-accesslog(/.*)?'sudo restorecon -Rv /var/lib/ldap-accesslogls -ldZ /var/lib/ldap-accesslogCreate the accesslog database without a hardcoded index (02-create-accesslog-database.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 eqThe 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:
sudo ldapadd -Q -Y EXTERNAL -H ldapi:/// \
-f 02-create-accesslog-database.ldifDiscover the assigned DN:
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}'
)printf 'Accesslog database DN: %s\n' "$ACCESSLOG_DN"Sample output:
Accesslog database DN: olcDatabase={3}mdb,cn=configIdentify the local SASL EXTERNAL identity:
Identity checks in this section use the ldapwhoami command.
sudo ldapwhoami -Q -Y EXTERNAL -H ldapi:///Sample output:
dn:gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=authCreate ACL and limits on each accesslog database (accesslog-access.ldif). Replace the EXTERNAL identity if ldapwhoami returns a different value:
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=unlimitedAccesslog records can contain modification data and should not be left readable through a broad default ACL.
Discover the module list DN:
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:
sudo ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// \
-b "$MODULE_DN" -s base olcModuleLoadCreate 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:
dn: REPLACE_WITH_MODULE_DN
changetype: modify
add: olcModuleLoad
olcModuleLoad: accesslog.laOn a fresh installation where both modules are absent, add them in separate modify operations or confirm that neither value exists before combining them:
dn: REPLACE_WITH_MODULE_DN
changetype: modify
add: olcModuleLoad
olcModuleLoad: syncprov.ladn: REPLACE_WITH_MODULE_DN
changetype: modify
add: olcModuleLoad
olcModuleLoad: accesslog.laAdd syncprov on the accesslog database (03-add-accesslog-syncprov.ldif):
dn: olcOverlay=syncprov,REPLACE_WITH_ACCESSLOG_DN
objectClass: olcOverlayConfig
objectClass: olcSyncProvConfig
olcOverlay: syncprov
olcSpReloadhint: TRUE
olcSpNopresent: TRUEConfigure syncprov and the accesslog overlay on the main database
Add indexes on the main database if they are missing:
dn: REPLACE_WITH_MDB_DN
changetype: modify
add: olcDbIndex
olcDbIndex: entryCSN eq
-
add: olcDbIndex
olcDbIndex: entryUUID eqAdd syncprov (04-add-main-syncprov.ldif):
dn: olcOverlay=syncprov,REPLACE_WITH_MDB_DN
objectClass: olcOverlayConfig
objectClass: olcSyncProvConfig
olcOverlay: syncprov
olcSpCheckpoint: 100 10
olcSpSessionlog: 1000Example 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):
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:00That 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:
sudo ldapmodify -Q -Y EXTERNAL -H ldapi:/// \
-f 01-load-replication-modules.ldifCreate the local cn=accesslog database on that node:
sudo ldapadd -Q -Y EXTERNAL -H ldapi:/// \
-f 02-create-accesslog-database.ldifDiscover ACCESSLOG_DN, then restrict who can read the change log:
sudo ldapmodify -Q -Y EXTERNAL -H ldapi:/// \
-f accesslog-access.ldifAdd syncprov on the accesslog database so delta consumers can synchronize against it:
sudo ldapadd -Q -Y EXTERNAL -H ldapi:/// \
-f 03-add-accesslog-syncprov.ldifAdd syncprov on the main database unless the provider-consumer guide already placed it there:
sudo ldapadd -Q -Y EXTERNAL -H ldapi:/// \
-f 04-add-main-syncprov.ldifFinally, attach the accesslog overlay to the main database so successful writes are logged:
sudo ldapadd -Q -Y EXTERNAL -H ldapi:/// \
-f 05-add-accesslog-overlay.ldifIf 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:
sudo ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b cn=config '(objectClass=olcOverlayConfig)' dn olcOverlay olcSpCheckpoint olcSpNoPresent olcSpReloadHintApply 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:
sudo install -d -m 0700 /root/openldap-backupExport the directory data that applications query:
sudo slapcat -b "dc=example,dc=com" \
-l /root/openldap-backup/example-com.ldifExport the local change log that delta-syncrepl reads:
sudo slapcat -b "cn=accesslog" \
-l /root/openldap-backup/accesslog.ldifExport the server configuration, including replication credentials in olcSyncrepl:
sudo slapcat -n 0 \
-l /root/openldap-backup/cn-config.ldifGenerate checksums for all three exports:
sudo sha256sum /root/openldap-backup/*.ldifStore 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:
sudo ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// \
-b "$MDB_DN" -s base olcSyncrepl olcUpdateRefWhen only one agreement exists—the usual case after the provider-consumer guide—delete the complete attributes without specifying a partial olcSyncrepl value:
vi remove-oneway-syncrepl.ldifdn: REPLACE_WITH_MDB_DN
changetype: modify
delete: olcSyncrepl
-
delete: olcUpdateRefWhen multiple Syncrepl agreements exist, do not delete the entire attribute. Copy and delete the exact full value of only the obsolete agreement.
sudo ldapmodify -Q -Y EXTERNAL -H ldapi:/// -f remove-oneway-syncrepl.ldifCreate the initial delta-syncrepl LDIF without olcMultiProvider:
umask 077
vi delta-syncrepl-initial.ldifOn ldap-client.example.com:
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=accesslogsudo ldapmodify -Q -Y EXTERNAL -H ldapi:/// -f delta-syncrepl-initial.ldifWait 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:
vi delta-syncrepl-reverse.ldifdn: 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: TRUEsudo ldapmodify -Q -Y EXTERNAL -H ldapi:/// -f delta-syncrepl-reverse.ldifEnable multi-provider on ldap-client.example.com in a separate file:
vi enable-multiprovider.ldifdn: REPLACE_WITH_MDB_DN
changetype: modify
add: olcMultiProvider
olcMultiProvider: TRUEsudo ldapmodify -Q -Y EXTERNAL -H ldapi:/// -f enable-multiprovider.ldifRestart 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:
sudo ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b "$MDB_DN" -s base olcSyncrepl olcMultiProviderRestart one node at a time:
sudo systemctl restart slapdsystemctl is-active slapdAfter a provider restart, inspect the unit with journalctl when replication or TLS errors persist:
sudo journalctl -u slapd -n 20 --no-pagerInitialize and Verify Multi-Provider Replication
Keep ldap-server.example.com authoritative during initialization.
Compare entry counts
Count DNs on the authoritative provider first:
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:
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:
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 contextCSNRepeat 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:
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-serverAdd, 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:
sudo ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b cn=accesslog '(objectClass=auditWriteObject)' reqStart reqType reqDN reqResult reqAuthzID | head -20The two providers maintain separate local accesslogs. Their contents are not expected to be identical.
Configure and Test MirrorMode
Applications
|
v
Write VIP or LDAP proxy
|
+---- Active writer: ldap-server.example.com
|
`---- Standby writer: ldap-client.example.comOperating 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
- Confirm writes currently reach
ldap-server.example.com. - Stop
slapdonldap-server.example.com. - Move the write endpoint to
ldap-client.example.com. - Perform a test write on
ldap-client.example.com. - Restore
ldap-server.example.com. - Verify that
ldap-server.example.comcatches up. - Compare
contextCSN. - Return it to service only after synchronization succeeds.
Test Failure Recovery and Conflict Risks
Test one provider offline
- Stop
slapdonldap-client.example.com. - Perform writes on
ldap-server.example.com. - Start
ldap-client.example.com. - Monitor its journal.
- Verify delta-syncrepl catch-up.
- 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:
sudo journalctl -u slapd -n 200 --no-pagerRead server IDs, syncrepl agreements, and multi-provider flags in one query:
sudo ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b cn=config '(|(olcServerID=*)(olcSyncrepl=*)(olcMultiProvider=TRUE))' dn olcServerID olcSyncrepl olcMultiProviderConfirm the expected overlays are loaded on each provider:
sudo ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b cn=config '(objectClass=olcOverlayConfig)' dn olcOverlayCheck that slapd is listening on the LDAP ports you configured:
sudo ss -ltnp | grep slapdConfirm clock synchronization when CSNs look out of order:
chronyc trackingReferences
- OpenLDAP Administrator's Guide — Replication — multi-provider, delta-syncrepl, and MirrorMode
- OpenLDAP overlays — Access Logging — accesslog overlay and purge
- slapo-accesslog(5) — accesslog overlay attributes
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.

