Slow LDAP searches usually mean a filter is running without a suitable index. Directory Server maintains separate index data for each indexed attribute on each database backend, and one attribute can carry several index types. Indexes accelerate matching searches but consume disk space and add work to add, modify, and delete operations.
This guide covers standard LDAP indexing in 389 Directory Server:
- Presence, equality, approximate, substring, and matching-rule indexes
- Choosing index types from real application filters
- Creating and modifying per-backend indexes
- Online and offline reindexing
- Default indexes for future databases
- Detecting unindexed and partially indexed searches
- Substring key tuning and safe index removal
Before you start:
- Install 389 Directory Server — running instance with a suffix (this lab uses
ldap1ondc=example,dc=com) - Manage users and groups —
ou=Peopleand theuid=user1test entry - Suffixes and backends — backend names and suffix mapping
- dsconf commands — online configuration
Tested on: Rocky Linux 10.2; 389 Directory Server 3.2.0.
The lab instance is ldap1 on ldap1.example.com with LDAP port 389. The primary backend is userroot for suffix dc=example,dc=com.
How LDAP indexes work in 389 Directory Server
LDAP search filter
|
v
Directory Server checks available index keys
|
+--> Suitable index exists
| |
| v
| Load matching entry IDs
|
+--> No usable index
|
v
Scan database entriesIndexes are configured per database backend:
Suffix: dc=example,dc=com
Backend: userrootAn index created in userroot does not configure the same attribute in another backend.
One attribute index entry can list several index types:
cn:
- pres
- eq
- subLDAP index types
| Index type | Identifier | Example filter | Use |
|---|---|---|---|
| Presence | pres |
(mail=*) |
Find entries that contain an attribute |
| Equality | eq |
(uid=user1) |
Match one normalized value |
| Approximate | approx |
(cn~=Jon Smith) |
Phonetic or approximate matching |
| Substring | sub |
(cn=*smith*) |
Match part of a value |
| Matching-rule index | OID-based | Extensible or language search | Specialized ordering or collation |
Equality indexes
Use equality indexes for frequent exact-value searches:
(uid=user1)
([email protected])
(employeeNumber=10025)
(member=uid=user1,ou=People,dc=example,dc=com)Presence indexes
Use presence indexes when applications search for entries that contain an attribute:
(mail=*)
(employeeNumber=*)Do not create a presence index only because an attribute exists on most entries. If nearly the whole database matches, the index may provide little benefit.
Approximate indexes
Use approximate indexes only when clients submit approximate filters such as (cn~=John Smith). Approximate matching uses a phonetic algorithm intended for US-ASCII English values. It is not a general multilingual or numeric search index.
Substring indexes
Use substring indexes for filters such as (cn=John*), (cn=*Smith), or (telephoneNumber=*555*). Substring indexes require substantially more keys and write work than equality indexes. They do not apply to binary attributes.
member or uniqueMember. When members are added or removed on a large group, substring maintenance can evaluate far more values than the single change. Use equality indexes for exact DN membership filters such as (member=uid=user1,ou=People,dc=example,dc=com).
Plan indexes from filters and find unindexed searches
Do not begin by indexing every attribute. Collect frequently used filters from application configuration, access logs, monitoring tools, SSSD or identity-client searches, administrative scripts, and group or role lookups.
| Search filter | Attribute | Required index |
|---|---|---|
(uid=user1) |
uid |
eq |
(mail=*) |
mail |
pres |
(telephoneNumber=*555*) |
telephoneNumber |
sub |
(cn~=John Smith) |
cn |
approx |
(member=USER_DN) |
member |
eq |
For compound filters such as (&(objectClass=inetOrgPerson)(employeeNumber=10025)), review index coverage for each filter component. The goal is not zero notes=U events at any cost. Index frequent and expensive queries without creating unnecessary write and storage overhead.
Review the access log on the server host:
/var/log/dirsrv/slapd-ldap1/accessSearch for index-related notes:
grep -E 'notes=.*(A|U)' /var/log/dirsrv/slapd-ldap1/access[17/Jul/2026:23:23:19 +0530] conn=18 op=1 RESULT err=0 tag=101 nentries=1 etime=0.002870207 notes=U details="Partially Unindexed Filter"Correlate SRCH and RESULT records by connection ID and operation ID. Record the search base, scope, filter, bind DN, entries returned, etime, and notes field.
| Indicator | Meaning |
|---|---|
notes=A |
All candidate filter attributes were unindexed and a full scan was required |
notes=U |
At least part of the search was unindexed, or an index exceeded the ID-list scan limit |
High etime |
The operation took longer to complete on the server |
notes=U does not always mean an index file is missing. A search can also be treated as partially unindexed when a matching index key produces more entry IDs than the configured ID-list scan limit allows. Keep ID-list limit tuning in search limits.
Before adding an index, run the target filter several times and record client time, server etime, returned entry count, and whether notes=A or notes=U appears. Repeat after reindexing instead of trusting one cached result. The roomNumber example in the next section shows a full before-and-after run.
Check existing database indexes
List indexes configured for userroot:
dsconf ldap1 backend index list userrootSample output:
dn: cn=cn,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config
cn: cn
nsIndexType: pres
nsIndexType: eq
nsIndexType: subInspect one attribute:
dsconf ldap1 backend index get userroot --attr roomNumberAlternatively, read the backend index configuration directly:
ldapsearch -x -H ldap://ldap1.example.com:389 -D "cn=Directory Manager" -W -b "cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config" -s one cn nsIndexType nsMatchingRule nsSystemIndexIdentify existing index types, system indexes, custom indexes, attributes configured but not rebuilt, and indexes no longer used by applications. Do not remove system or default operational indexes only because they do not appear in an application search log.
Create and modify indexes
Use roomNumber as the primary custom-index example because it is not part of the default index set and supports straightforward equality, presence, and substring tests.
Confirm roomNumber has no index entry yet:
dsconf ldap1 backend index get userroot --attr roomNumberOn a fresh backend index get returns no nsIndexType lines for the attribute. If a previous test left configuration behind, remove it with dsconf ldap1 backend index delete --attr roomNumber userroot before continuing.
Assign a test value on the standard lab entry. Save add-roomNumber.ldif:
dn: uid=user1,ou=People,dc=example,dc=com
changetype: modify
add: roomNumber
roomNumber: 401ldapmodify -x -H ldap://ldap1.example.com:389 -D "cn=Directory Manager" -W -f add-roomNumber.ldifThe modify completes with no output when the attribute is added successfully.
Run the target filter before creating any index:
ldapsearch -x -H ldap://ldap1.example.com:389 -D "cn=Directory Manager" -W -b "dc=example,dc=com" "(roomNumber=401)" dn roomNumberdn: uid=user1,ou=People,dc=example,dc=com
roomNumber: 401Correlate the matching SRCH and RESULT lines in /var/log/dirsrv/slapd-ldap1/access by connection ID and operation ID:
conn=93 op=1 SRCH base="dc=example,dc=com" scope=2 filter="(roomNumber=401)" attrs="distinguishedName roomNumber"
conn=93 op=1 RESULT err=0 tag=101 nentries=1 etime=0.035569932 notes=U details="Partially Unindexed Filter"Directory Server logged notes=U because roomNumber had no equality index yet. A filter where every candidate attribute is unindexed can also appear as notes=A.
Create an equality index and rebuild existing data immediately:
dsconf ldap1 backend index add --attr roomNumber --index-type eq --reindex userrootIndex task index_attrs_2026-07-17T23:38:24.821495 completed successfully
Successfully added indexCreating the configuration without rebuilding does not populate index keys for entries that already existed.
Verify the new index:
dsconf ldap1 backend index get userroot --attr roomNumbernsIndexType: eqRerun the same LDAP search:
ldapsearch -x -H ldap://ldap1.example.com:389 -D "cn=Directory Manager" -W -b "dc=example,dc=com" "(roomNumber=401)" dn roomNumberdn: uid=user1,ou=People,dc=example,dc=com
roomNumber: 401The LDAP result is unchanged. The correlated access-log RESULT line no longer carries an unindexed note:
conn=100 op=1 SRCH base="dc=example,dc=com" scope=2 filter="(roomNumber=401)" attrs="distinguishedName"
conn=100 op=1 RESULT err=0 tag=101 nentries=1 etime=0.000376217Server etime dropped from 0.035569932 to 0.000376217 in this small lab. On a tiny suffix the client may not see a meaningful timing difference, but the access log confirms the filter is now fully indexed.
Add a presence index type to the same attribute:
dsconf ldap1 backend index set --attr roomNumber --add-type pres userrootRebuild the attribute:
dsconf ldap1 backend index reindex --attr roomNumber --wait userrootIndex task index_attrs_2026-07-17T23:40:28.976951 completed successfully
Successfully reindexed databaseRemove one index type when it is no longer needed:
dsconf ldap1 backend index set --attr roomNumber --del-type pres userrootReindex again after changing index types. The same workflow applies to common production attributes such as uid, mail, employeeNumber, and member in userroot or any other backend where applications search. Attributes such as cn and telephoneNumber are already present in the default index set on a normal userroot backend, so use index set --add-type rather than index add when extending them.
Tune 389 Directory Server substring indexes
Continue with roomNumber, which already has an equality index from the previous section. Add substring indexing when wildcard searches on that attribute are frequent:
dsconf ldap1 backend index set --attr roomNumber --add-type sub --reindex userrootIndex task index_attrs_2026-07-17T23:39:06.803455 completed successfully
Index successfully updatedThe 389 DS 3.2.0 CLI uses index add for a new entry and index set --add-type for an existing one. This section extends roomNumber; it does not run index add on telephoneNumber.
telephoneNumber and cn are part of the standard default index set. A normal userroot backend already has index entries for those attributes, so index add would fail with a duplicate entry. Inspect an existing attribute first:
dsconf ldap1 backend index get userroot --attr telephoneNumberUse index set --add-type only when the required type is absent.
Test distinct substring patterns on roomNumber:
(roomNumber=40*)
(roomNumber=*01*)
(roomNumber=*401)By default, the minimum substring search-key length is three characters. Filters with shorter fragments might not use the substring index as expected.
Change the substring search-key length
Save substring-key-length.ldif:
dn: cn=roomNumber,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config
changetype: modify
add: objectClass
objectClass: extensibleObject
-
add: nsSubStrBegin
nsSubStrBegin: 2
-
add: nsSubStrMiddle
nsSubStrMiddle: 2
-
add: nsSubStrEnd
nsSubStrEnd: 2ldapmodify -x -H ldap://ldap1.example.com:389 -D "cn=Directory Manager" -W -f substring-key-length.ldifThe modify completes with no output when the substring key settings are accepted.
Rebuild the attribute:
dsconf ldap1 backend index reindex --attr roomNumber --wait userroot| Setting | Pattern affected |
|---|---|
nsSubStrBegin |
abc* |
nsSubStrMiddle |
*abc* |
nsSubStrEnd |
*abc |
Shorter search keys create more index keys and increase index size, memory pressure, and update cost.
Approximate and matching-rule indexes
Create an approximate index only when applications issue approximate filters. The cn attribute already has an index entry on a normal userroot backend:
dsconf ldap1 backend index get userroot --attr cnnsIndexType: pres
nsIndexType: eq
nsIndexType: subAdd approx to the existing entry rather than running index add:
dsconf ldap1 backend index set --attr cn --add-type approx --reindex userroot--add-type and --reindex are supported on index set in 389 DS 3.2.0.
Index task index_attrs_2026-07-17T23:39:12.291897 completed successfully
Index successfully updatedTest:
ldapsearch -x -H ldap://ldap1.example.com:389 -D "cn=Directory Manager" -W -b "dc=example,dc=com" "(cn~=Jon Smith)" dn cnApproximate matching uses a phonetic algorithm intended for US-ASCII English values. Create the index only when clients actually submit ~= filters.
For international or extensible matching, confirm the attribute syntax, obtain the matching-rule OID, and add it to the index configuration. The 3.2.0 CLI exposes --matching-rule on index add and --add-mr on index set.
For a new index entry:
dsconf ldap1 backend index add --attr ATTRIBUTE --index-type eq --matching-rule MATCHING_RULE_OID --reindex userrootFor an existing index entry:
dsconf ldap1 backend index set --attr ATTRIBUTE --add-mr MATCHING_RULE_OID --reindex userrootReindex the attribute and test the exact extensible filter the client generates. Keep the complete explanation of LDAP filter syntax and extensible matching in your search-filter documentation.
Reindex online and offline
Online reindex while Directory Server is running
Reindex one attribute:
dsconf ldap1 backend index reindex --attr roomNumber --wait userrootMonitor /var/log/dirsrv/slapd-ldap1/errors while the task runs. Online reindexing keeps the server available, but searches involving the rebuilding attribute can be incomplete or inconsistent until the task finishes. Schedule large online rebuilds during reduced activity when temporary inconsistency is acceptable.
Offline rebuild with dsctl db2index
Stop the instance:
Offline instance work in this section uses dsctl commands.
dsctl ldap1 stopRebuild selected attributes:
dsctl ldap1 db2index userroot --attr roomNumber employeeNumberRebuild all configured indexes:
dsctl ldap1 db2index userrootStart the instance:
dsctl ldap1 startUse offline rebuilding when search consistency must be preserved, many indexes need rebuilding, an index may be damaged, or a maintenance window is available. Back up the database before major index repairs.
Default indexes for new databases
Default indexes are stored under:
cn=default indexes,cn=config,cn=ldbm database,cn=plugins,cn=configList them:
ldapsearch -x -H ldap://ldap1.example.com:389 -D "cn=Directory Manager" -W -b "cn=default indexes,cn=config,cn=ldbm database,cn=plugins,cn=config" -s one cn nsIndexType nsSystemIndexAdd a default index for future backends. Save default-roomNumber-index.ldif:
dn: cn=roomNumber,cn=default indexes,cn=config,cn=ldbm database,cn=plugins,cn=config
objectClass: top
objectClass: nsIndex
cn: roomNumber
nsSystemIndex: false
nsIndexType: eq
nsIndexType: subldapadd -x -H ldap://ldap1.example.com:389 -D "cn=Directory Manager" -W -f default-roomNumber-index.ldifadding new entry "cn=roomNumber,cn=default indexes,cn=config,cn=ldbm database,cn=plugins,cn=config"Verify the new default index entry:
ldapsearch -x -H ldap://ldap1.example.com:389 -D "cn=Directory Manager" -W -b "cn=default indexes,cn=config,cn=ldbm database,cn=plugins,cn=config" -s one "(cn=roomNumber)" cn nsIndexType nsSystemIndexdn: cn=roomNumber,cn=default indexes,cn=config,cn=ldbm database,cn=plugins,cn=config
cn: roomNumber
nsIndexType: eq
nsIndexType: sub
nsSystemIndex: falseRed Hat documents that default indexes are maintained through LDIF and copied only into newly created databases. Default index changes do not update backends that already exist. Configure and reindex existing backends separately. Use default indexes only for attributes that should consistently be indexed in every future backend.
Remove indexes, monitor overhead, and troubleshoot
Before removing an index:
- Search access logs for use of the attribute.
- Identify all relevant filter types.
- Record the existing configuration.
- Test in a nonproduction environment.
- Remove one index type rather than the whole attribute where possible.
- Rebuild the remaining index.
- Monitor for
notes=A,notes=U, and increasedetime.
Remove one index type:
dsconf ldap1 backend index set --attr description --del-type sub userrootRemove the complete attribute index:
dsconf ldap1 backend index delete --attr description userrootDo not remove system indexes or frequently used default indexes solely to save disk space.
More indexes generally mean faster matching searches, larger database files, slower writes, and longer imports and rebuilds. Pay particular attention to substring indexes, attributes with many values, frequently modified attributes, large group membership attributes, and indexes that match nearly every entry.
| Symptom | Likely cause | Fix |
|---|---|---|
| Search returns inconsistent results after a change | Online reindex still running | Check errors log; complete offline db2index if consistency is required |
| Equality is fast but wildcard search is slow | Only eq exists for the attribute |
Add sub only after confirming the wildcard search is frequent and necessary |
| Two-character substring search stays unindexed | Default three-character key length | Tune nsSubStrBegin, nsSubStrMiddle, nsSubStrEnd and reindex |
| Adds and modifies became slower | Recent sub, approx, or broad pres indexes |
Remove unused types; avoid substring indexes on member or uniqueMember |
| Index exists in one suffix but not another | Indexes are per backend | Configure and rebuild in every backend applications search |
| Existing entries missing but new entries match | Configuration without completed rebuild | Reindex the attribute and verify task completion |
What's next
After you complete this guide, continue with:
- Configure Virtual List View Indexes in 389 Directory Server — virtual list views for paged browsing
- Configure LDAP Search Limits in 389 Directory Server — lookthrough and unindexed search controls
- Tune LMDB, Entry Cache and DN Cache in 389 Directory Server — LMDB tuning after index growth
- Benchmark 389 Directory Server Performance with ldclt and logconv — measure search latency before and after indexing
- Tune Large LDAP Groups and memberOf Performance in 389 Directory Server — group-related search patterns
Summary
- Collect real search filters and access-log evidence.
- Map each filter to the required index type.
- Create the index in the correct backend.
- Reindex existing data.
- Retest the exact application query.
- Monitor search speed, disk usage, and write latency.
- Tune substring keys only for proven short-fragment searches.
- Remove unused indexes cautiously.
- Keep VLV and ID-list-limit tuning in their dedicated articles.
Directory Server supports presence, equality, approximate, substring, and OID-based matching-rule indexes. Approximate indexes target English phonetic searches. Substring indexes are more expensive to maintain and are inappropriate for member and uniqueMember. Online reindex keeps the server available but can return inconsistent results until the task completes; offline dsctl db2index is safer when consistency matters.
References
- Red Hat Directory Server 13 — Managing indexes in Directory Server (§6)
- Red Hat Directory Server 13 — Access-log search indicators

