Anonymous LDAP clients can discover your server, enumerate naming contexts, and read directory data without credentials when ACIs permit it. Hardening requires two layers:
- Server bind policy through
nsslapd-allow-anonymous-access - Entry and attribute permissions through
acivalues, especially rules that useuserdn="ldap:///anyone"
This guide shows how to audit current exposure, restrict anonymous access to the root DSE or disable it completely, grant only selected public attributes when required, and block unauthenticated empty-password binds that can grant the supplied account's permissions without verifying its password when the feature is enabled.
It builds on ACI examples for syntax and complements advanced ACI bind rules, TLS and StartTLS for transport protection, and effective rights for detailed permission analysis.
Before you start:
- ACI examples —
ldapmodify, rights, anduserdnexpressions - Manage users and groups — lab suffix
dc=example,dc=comon instanceldap1 - Self-service password and profile ACIs — prepares
user1,/root/user1.pw, and the own-entry read ACI used by the authenticated verification - dsconf commands — online configuration changes
ldap:///anyone ACIs for public reads. It does not replace general ACI design, SSF or network bind rules, TLS configuration, anonymous resource limits, or Get Effective Rights analysis. See the dedicated chapters linked above.
Tested on: Rocky Linux 10.2; 389 Directory Server 3.2.0.
The plain ldap://localhost commands in this guide are for an isolated local lab on instance ldap1 (port 389, suffix dc=example,dc=com). Use StartTLS or LDAPS when connecting across a network.
Changes to nsslapd-allow-anonymous-access do not take effect until you restart the instance. Run dsctl ldap1 restart after every mode change in this guide before you verify behaviour.
Anonymous vs authenticated vs unauthenticated binds
Directory Server evaluates bind policy before ACIs. Test each bind style separately. They are not interchangeable.
| Bind type | Bind DN | Password | Server settings involved |
|---|---|---|---|
| Anonymous bind | Empty | Empty | nsslapd-allow-anonymous-access |
| Unauthenticated bind | Non-empty | Empty | nsslapd-allow-unauthenticated-binds |
| Authenticated simple bind | Non-empty | Valid password | Normal bind and ACI evaluation |
An anonymous bind establishes an anonymous authorization identity. An unauthenticated bind supplies a DN but no password. When nsslapd-allow-unauthenticated-binds is enabled, Red Hat Directory Server can accept an empty-password bind for the supplied account, including Directory Manager, and grant that account's permissions without verifying a password.
How anonymous access is controlled
| Layer | Control | What it decides |
|---|---|---|
| Server bind policy | nsslapd-allow-anonymous-access |
Whether empty DN and empty password binds are accepted, and whether root DSE access alone is permitted. Requires an instance restart. |
| Server bind policy | nsslapd-allow-unauthenticated-binds |
Whether a non-empty DN with an empty password is accepted. Takes effect without a restart. |
| Entry and attribute permissions | aci attributes |
What the current authorization identity may read, search, compare, or modify |
Accepting an anonymous bind does not make every entry public. Applicable ACIs still control each operation. Conversely, setting nsslapd-allow-anonymous-access to off blocks anonymous binds before ACIs are evaluated, even when a ldap:///anyone rule exists on an entry.
Use ldap:///anyone when the permission should apply to anonymous and authenticated clients. Use ldap:///all when only successfully authenticated binds should match.
Audit current anonymous exposure
Read the server-level settings on ldap1:
dsconf ldap1 config get nsslapd-allow-anonymous-access nsslapd-allow-unauthenticated-bindsSample output:
nsslapd-allow-anonymous-access: on
nsslapd-allow-unauthenticated-binds: offReview ACIs that may grant public access with ldapsearch. Search the entire suffix subtree and inspect every value that references ldap:///anyone:
ldapsearch -LLL -o ldif-wrap=no -x -H ldap://localhost:389 -D "cn=Directory Manager" -y /root/dm.pw -b "dc=example,dc=com" -s sub "(aci=*)" dn aciInspect every returned aci value containing ldap:///anyone. Repeat this search under every user-data suffix. For a server-wide audit, also inspect ACIs under cn=config, because feature and control ACIs are outside the normal data suffixes:
ldapsearch -LLL -o ldif-wrap=no -x -H ldap://localhost:389 -D "cn=Directory Manager" -y /root/dm.pw -b "cn=config" -s sub "(aci=*)" dn aciInspect returned values containing ldap:///anyone. The suffix search and cn=config search serve different purposes; keep both.
Sample output (truncated):
dn: dc=example,dc=com
aci: (targetattr="dc || description || objectClass")(targetfilter="(objectClass=domain)")(version 3.0; acl "Enable anyone domain read"; allow (read, search, compare)(userdn="ldap:///anyone");)
dn: ou=People,dc=example,dc=com
aci: (targetattr="uid || cn || sn || mail || telephoneNumber || description || objectClass || gidNumber || uidNumber || homeDirectory || loginShell")...The domain rule lets anonymous clients read selected attributes on objectClass=domain entries. Delegated-administration and tenant rules on descendant entries appear in the same result set.
To examine ACIs on the suffix entry alone, repeat the search with base scope:
ldapsearch -LLL -o ldif-wrap=no -x -H ldap://localhost:389 -D "cn=Directory Manager" -y /root/dm.pw -b "dc=example,dc=com" -s base "(objectClass=*)" aciQuery the root DSE without credentials. Clients use this step to discover namingContexts and LDAP version support:
ldapsearch -LLL -x -H ldap://localhost:389 -b "" -s base "(objectClass=*)" namingContexts supportedLDAPVersionSample output:
dn:
namingContexts: dc=example,dc=com
supportedLDAPVersion: 2
supportedLDAPVersion: 3Search the directory suffix without credentials:
ldapsearch -LLL -x -H ldap://localhost:389 -b "dc=example,dc=com" "(objectClass=*)" dnSample output:
dn: dc=example,dc=com
dn: dc=tenant1,ou=Tenants,dc=example,dc=com
dn: dc=tenant2,ou=Tenants,dc=example,dc=comAnonymous clients receive DNs for entries the current ACIs expose. In this lab that means domain entries through the ldap:///anyone rule, not necessarily every user entry.
Search for user entries under ou=people without credentials:
ldapsearch -LLL -x -H ldap://localhost:389 -b "ou=people,dc=example,dc=com" "(objectClass=*)" dn cn mailThe command exits successfully but returns no entries when no ACI grants anonymous access to people entries.
Filter on a sensitive attribute anonymously to see whether search rights exist without read rights. First confirm that matching data exists as Directory Manager:
ldapsearch -LLL -x -H ldap://localhost:389 -D "cn=Directory Manager" -y /root/dm.pw -b "ou=people,dc=example,dc=com" "(mail=*)" dn mailSample output:
dn: uid=user1,ou=people,dc=example,dc=com
mail: [email protected]
dn: uid=user2,ou=people,dc=example,dc=com
mail: [email protected]Then run the same filter anonymously:
ldapsearch -LLL -x -H ldap://localhost:389 -b "ou=people,dc=example,dc=com" "(mail=*)" dnWhen the administrative search confirms that matching entries exist, an empty anonymous result indicates that the current anonymous permissions do not allow those entries to be matched and returned. Review both search permission on mail and any other ACIs affecting entry visibility.
Configure the anonymous-access mode
| Value | Behaviour |
|---|---|
on |
Accept anonymous binds; ACIs determine what anonymous clients can read |
rootdse |
Permit anonymous root DSE access only; reject anonymous searches against directory entries |
off |
Reject anonymous binds completely, including root DSE queries |
rootdse is a practical default when clients need LDAP server discovery through namingContexts but normal directory data should require authentication.
Restrict anonymous access to the root DSE
I'll set rootdse on ldap1 and restart the instance:
dsconf ldap1 config replace nsslapd-allow-anonymous-access=rootdseThe instance must restart before this bind-policy change takes effect:
dsctl ldap1 restartSample output:
Successfully replaced value(s) for 'nsslapd-allow-anonymous-access': 'rootdse'
Instance "ldap1" has been restartedThe replace and restart succeeded. Anonymous clients can read the root DSE again, but suffix searches should now fail.
ldapsearch -LLL -x -H ldap://localhost:389 -b "" -s base "(objectClass=*)" namingContexts supportedLDAPVersionSample output:
dn:
namingContexts: dc=example,dc=com
supportedLDAPVersion: 2
supportedLDAPVersion: 3Confirm anonymous suffix searches are rejected:
ldapsearch -LLL -x -H ldap://localhost:389 -b "dc=example,dc=com" "(objectClass=*)" dnSample output:
Inappropriate authentication (48)
Additional information: Anonymous access is not allowed.Result code 48 confirms anonymous suffix searches are blocked while root DSE discovery still works under rootdse.
Confirm authenticated users still search according to their ACIs. This test assumes user1, /root/user1.pw, and an applicable read ACI were prepared in the self-service ACI chapter. Otherwise, use any authenticated account and password file that can read a known entry.
ldapsearch -LLL -x -H ldap://localhost:389 -D "uid=user1,ou=people,dc=example,dc=com" -y /root/user1.pw -b "uid=user1,ou=people,dc=example,dc=com" -s base "(objectClass=*)" cn mailThe result depends on self-service or other ACIs configured for user1. A successful authenticated search confirms the global anonymous restriction did not remove normal user access.
Disable anonymous binds completely
When no client requires anonymous discovery, set the policy to off and restart:
dsconf ldap1 config replace nsslapd-allow-anonymous-access=offRestart ldap1 so anonymous binds are rejected immediately:
dsctl ldap1 restartVerify with an anonymous suffix search:
ldapsearch -LLL -x -H ldap://localhost:389 -b "dc=example,dc=com"Sample output:
ldap_bind: Inappropriate authentication (48)
additional info: Anonymous access is not allowedRed Hat Directory Server documents this result when anonymous access is disabled. Root DSE queries fail the same way under off:
ldapsearch -LLL -x -H ldap://localhost:389 -b "" -s base "(objectClass=*)" namingContextsSample output:
ldap_bind: Inappropriate authentication (48)
additional info: Anonymous access is not allowedConfigure narrow public access
When a phone book or public staff directory is required, enable anonymous binds only for the ACI example, then create a dedicated subtree and grant read access to selected attributes.
Enable anonymous access for the public-directory example:
dsconf ldap1 config replace nsslapd-allow-anonymous-access=onRestart the instance so the server accepts anonymous binds for the ACI tests below:
dsctl ldap1 restartSuitable public fields include:
cn
sn
givenName
telephoneNumberSave the following as /tmp/public-ou.ldif:
dn: ou=Public,dc=example,dc=com
objectClass: organizationalUnit
ou: Public
dn: cn=Jane Public,ou=Public,dc=example,dc=com
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: person
objectClass: top
cn: Jane Public
sn: Public
givenName: Jane
telephoneNumber: +1-555-0100
mail: [email protected]Create the container and sample entry as Directory Manager:
ldapadd -x -H ldap://localhost:389 -D "cn=Directory Manager" -y /root/dm.pw -f /tmp/public-ou.ldifSample output:
adding new entry "ou=Public,dc=example,dc=com"
adding new entry "cn=Jane Public,ou=Public,dc=example,dc=com"Both entries were accepted. The public-directory ACI in the next step applies only to ou=Public.
dn: ou=Public,dc=example,dc=com
changetype: modify
add: aci
aci: (targetattr="cn || sn || givenName || telephoneNumber")(version 3.0; acl "Allow public directory lookup"; allow (read,search,compare) userdn="ldap:///anyone";)Apply the ACI:
ldapmodify -x -H ldap://localhost:389 -D "cn=Directory Manager" -y /root/dm.pw -f /tmp/public-aci.ldifInclude objectClass in targetattr with search permission when clients use (objectClass=*) filters. This example uses an attribute the ACI already permits:
ldapsearch -LLL -x -H ldap://localhost:389 -b "ou=Public,dc=example,dc=com" "(cn=Jane Public)" cn sn givenName telephoneNumber mailSample output:
dn: cn=Jane Public,ou=Public,dc=example,dc=com
cn: Jane Public
sn: Public
givenName: Jane
telephoneNumber: +1-555-0100mail is omitted because it is outside the allow list. Anonymous clients receive only the public attributes you named.
Protect sensitive attributes from anonymous searches
Do not expose attributes such as:
userPassword
mail
mobile
homePhone
postalAddress
employeeNumber
memberOf
nsRole
aciPrefer an explicit targetattr allow list. Avoid targetattr="*" with a short deny list. That pattern is difficult to audit and easy to over-grant.
Test both direct reads and filter searches. Granting read without search, or the reverse, produces different client behaviour. Verify that sensitive attributes cannot be used in anonymous filters when policy requires it.
Remove a public-access ACI safely
Follow the safe update workflow from ACI examples:
- List every
acivalue on the affected entry withldapsearch. - Identify the exact public-access rule. For example, look for
Enable anyone domain readondc=example,dc=com. - Delete only that value with a targeted
delete: acimodify operation. - Retest anonymous and authenticated searches.
The following LDIF removes only the Allow public directory lookup ACI created in this lab. To remove another public rule, retrieve and copy that rule's exact stored value into the delete: aci operation.
Save the following as /tmp/remove-public-aci.ldif:
dn: ou=Public,dc=example,dc=com
changetype: modify
delete: aci
aci: (targetattr="cn || sn || givenName || telephoneNumber")(version 3.0; acl "Allow public directory lookup"; allow (read,search,compare) userdn="ldap:///anyone";)Apply the targeted ACI delete as Directory Manager:
ldapmodify -x -H ldap://localhost:389 -D "cn=Directory Manager" -y /root/dm.pw -f /tmp/remove-public-aci.ldifDo not delete the entire aci attribute unless you intend to remove every rule on the entry.
Disable unauthenticated empty-password binds
An unauthenticated bind supplies a non-empty bind DN with an empty password. This is different from an anonymous bind, which supplies neither a DN nor a password.
Keep unauthenticated binds disabled:
dsconf ldap1 config replace nsslapd-allow-unauthenticated-binds=offThis setting takes effect without restarting the instance. Red Hat Directory Server documents off as the default.
Verify that a DN with an empty password is rejected:
ldapwhoami -x -H ldap://localhost:389 -D "uid=user1,ou=people,dc=example,dc=com" -w ''Sample output:
ldap_bind: Server is unwilling to perform (53)
additional info: Unauthenticated binds are not allowedDo not enable nsslapd-allow-unauthenticated-binds on a shared or production server. When enabled, Directory Server can accept an empty password for a supplied account DN and grant that account's permissions, including a privileged account, without verifying the password.
Production considerations
Before you change production policy, test clients that may rely on anonymous LDAP:
- SSSD and operating-system identity stacks
- LDAP address-book applications
- Monitoring and load-balancer health checks
- Legacy applications that read
namingContextsbefore binding
Prefer rootdse over off when a client needs anonymous server discovery but must not read directory entries. A second instance on the same host, such as ldap2 on port 1389 from multiple instances, can hold a separate policy for isolated client testing without changing production ldap1 settings.
Even with narrow ACIs, anonymous clients can consume server resources. Directory Server can limit anonymous search result size, execution time, idle time, and the number of candidate entries examined by configuring a limit template and referencing it through nsslapd-anonlimitsdn. Configure those controls in the dedicated resource limits chapter when anonymous access must remain enabled under strict quotas.
Verify and finalize the policy
After hardening, confirm behaviour matches intent:
| Test | Expected result |
|---|---|
| Anonymous root DSE search | Allowed with on or rootdse; denied with off |
| Anonymous suffix search | Denied with rootdse or off; depends on ACIs with on |
| Anonymous public-attribute search | Allowed only under entries with a narrow ldap:///anyone ACI |
| Anonymous sensitive-attribute search | Denied unless explicitly and intentionally granted |
| Valid authenticated user search | Controlled by normal ACIs—not by the anonymous bind settings |
| Bind DN with empty password | Denied when nsslapd-allow-unauthenticated-binds is off |
| Valid bind DN and password | Allowed when credentials and ACIs permit the operation |
| Symptom | Likely cause | Fix |
|---|---|---|
| Anonymous searches still return entries | Inherited ACI contains userdn="ldap:///anyone" |
Search aci on ancestors with subtree scope; delete or narrow the public rule |
| Root DSE search fails after hardening | Mode is off instead of rootdse |
Set nsslapd-allow-anonymous-access=rootdse and restart when discovery is required |
| Mode change has no effect | Instance not restarted after changing nsslapd-allow-anonymous-access |
Run dsctl ldap1 restart before retesting |
| Application stops working after hardening | Client performs anonymous discovery or pre-bind searches | Test with rootdse; update the client to bind before searching |
| Authenticated users also lose access | An ACI was deleted or modified during cleanup | Restore the intended ACI; anonymous bind settings do not replace user ACIs |
| Empty-password bind succeeds with account permissions | nsslapd-allow-unauthenticated-binds is on |
Set the value to off; treat this setting as substantially more dangerous than anonymous bind |
| Anonymous user can search by a hidden attribute | search granted without read, or vice versa, through another ACI |
Review inherited rules; remove unintended search or compare on sensitive attributes |
| Users can display the complete membership list | An ACI grants read on member |
Remove or narrow the read grant if membership must remain private |
| Users can test or infer whether a DN is a member | An ACI grants search or compare on member |
Remove or narrow those rights when membership queries must also be restricted |
Result code 48 in this workflow normally means the active anonymous-access mode rejected the bind. Confirm the configured value with dsconf ldap1 config get nsslapd-allow-anonymous-access and verify that the instance was restarted. For result code 50 after a successful bind, see fix LDAP error 50.
Set the final anonymous-access policy and clean up
Remove lab objects when you no longer need them:
ldapdelete -x -H ldap://localhost:389 -D "cn=Directory Manager" -y /root/dm.pw "cn=Jane Public,ou=Public,dc=example,dc=com"Delete the empty public container after the sample entry is gone:
ldapdelete -x -H ldap://localhost:389 -D "cn=Directory Manager" -y /root/dm.pw "ou=Public,dc=example,dc=com"Use on only when you deliberately retain public ldap:///anyone access and have reviewed every applicable ACI:
# Optional: use only when intentionally retaining public anonymous access
dsconf ldap1 config replace nsslapd-allow-anonymous-access=onRestart after that optional change if you enable full anonymous access again:
dsctl ldap1 restartLeave the lab in a hardened state. Set rootdse as the default final policy. That is the last mode change in this guide.
dsconf ldap1 config replace nsslapd-allow-anonymous-access=rootdseRestart once more so the final rootdse policy is active:
dsctl ldap1 restartVerify root DSE discovery succeeds and suffix searches fail:
ldapsearch -LLL -x -H ldap://localhost:389 -b "" -s base "(objectClass=*)" namingContextsSample output:
dn:
namingContexts: dc=example,dc=comConfirm that anonymous clients can no longer search the data suffix:
ldapsearch -LLL -x -H ldap://localhost:389 -b "dc=example,dc=com" "(objectClass=*)" dnSample output:
Inappropriate authentication (48)
Additional information: Anonymous access is not allowed.Remove /root/user1.pw only when you created it specifically for this lab and no later chapter requires it.
# Optional lab cleanup
rm -f /root/user1.pwWhat's Next
- Require secure LDAP connections — secure binds are separate from anonymous search policy
- ACI examples — public read ACIs when anonymous access stays enabled
- Get effective rights — inspect anonymous effective rights on sensitive entries
References
- Red Hat Directory Server 13 — Core server configuration attributes
- Red Hat Directory Server 13 — Managing access control
- Red Hat Directory Server 13 — Defining bind rules
- 389 Directory Server — Access Control Design
Summary
- Audit anonymous searches and every
ldap:///anyoneACI before you change bind policy. - Restart
ldap1after everynsslapd-allow-anonymous-accesschange before you verify results. - Use
rootdsewhen clients need anonymous server discovery without reading directory entries. - Use
offwhen anonymous binds must be rejected completely. - Grant public access only through explicit attribute allow lists on dedicated entries.
- Keep
nsslapd-allow-unauthenticated-bindsdisabled. Enabling it can grant a supplied account's permissions without a password. - Test applications before and after changing policy; distinguish
ldap:///anyonefromldap:///all.

