Configure OpenLDAP Password Policy with ppolicy

Configure OpenLDAP ppolicy for password expiration, warnings, history, account lockout, grace logins, forced resets, and SSSD client behavior.

Published

Updated

Read time 22 min read

Reviewed byDeepak Prasad

OpenLDAP ppolicy password expiration, history and account lockout configuration

OpenLDAP does not enforce password expiration, reuse prevention, or failed-login lockout merely because user entries contain userPassword. These controls are provided by the ppolicy overlay, which implements the LDAP Password Policy Internet-Draft rather than a finalized RFC.

This guide explains how to:

  • Load and verify the ppolicy module on the RHEL family
  • Attach the overlay to the MDB database
  • Create pwdPolicy entries and assign them in OpenLDAP 2.6
  • Enforce minimum length, history, expiration, warnings, and lockout
  • Initialize existing users for password aging
  • Unlock accounts and force post-reset password changes
  • Understand SSSD client behavior and replication caveats

The procedure applies to OpenLDAP 2.6 deployments across Red Hat Enterprise Linux, Rocky Linux, AlmaLinux, Oracle Linux, and CentOS Stream.

Tested on: Rocky Linux 10.2 with openldap-servers 2.6.10-1.el10_2 from EPEL 10.2. Verified pwdMinLength: 15, short-password rejection, password-history enforcement, failed-bind lockout, pwdFailureTime delete protection, delegated-administrator unlock and forced reset (pwdReset), and pwdPolicySubentry assignment on this host after applying the split ACL examples below.

Lab setting Value
OpenLDAP server ldap-server.example.com
Authentication client ldap-client.example.com
Directory suffix dc=example,dc=com
Users container ou=people,dc=example,dc=com
System accounts container ou=system,dc=example,dc=com
Policies container ou=policies,dc=example,dc=com
Default policy cn=default,ou=policies,dc=example,dc=com
Delegated password administrator uid=pwdadmin,ou=system,dc=example,dc=com
Test user uid=jdoe,ou=people,dc=example,dc=com
Data backend MDB
Configuration system cn=config
LDAP transport StartTLS on TCP port 389
CA certificate /etc/openldap/certs/example-ldap-ca.crt

Complete these guides first:

IMPORTANT
Test password-policy enforcement using a normal user or a delegated password administrator. The database root DN can bypass parts of the password-policy process and is not a reliable policy test identity. When a bind fails before policy attributes are evaluated, follow Fix OpenLDAP invalid credentials Error 49.
Your goal Go to
Understand how ppolicy fits together How OpenLDAP ppolicy Works
Plan policy values before writing LDIF Plan the Password Policy
Load the module and overlay Load the ppolicy Module and Overlay
Create and assign policies Create and Assign the Default Policy
Test length, history, and lockout Test the Policy
Check SSSD behavior Operate and Monitor ppolicy
Fix a policy that appears inactive Troubleshooting

How OpenLDAP ppolicy Works

Password binds and password changes pass through the ppolicy overlay before the MDB backend stores the result.

text
Password bind or password change
              |
              v
       ppolicy overlay
              |
              +-- Select effective policy
              |
              +-- Check password age
              +-- Check password history
              +-- Check password length
              +-- Record failed binds
              +-- Lock or unlock account
              +-- Return warning or grace-login control
              |
              v
          MDB database

Module, overlay and policy entry

Three layers work together:

Layer Purpose
ppolicy module Makes password-policy functionality available to slapd
ppolicy overlay Applies policy behavior to one database
pwdPolicy entry Stores expiration, history, and lockout values

Important behavior to keep in mind:

  • The overlay applies to the database beneath which it is configured.
  • A policy entry is normal directory data beneath the application suffix, not part of cn=config.
  • Policy controls userPassword; OpenLDAP does not accept another password attribute for pwdAttribute.
  • Password-policy operational state is stored on individual user entries.
  • The ppolicy implementation enforces one userPassword value even though the base schema permits multiple values on that attribute.

The ppolicy overlay does not replace ACLs. The overlay evaluates password-policy state during binds and password changes, while ACLs determine who may modify userPassword and protected operational attributes. See OpenLDAP ACL configuration with practical examples for the complete olcAccess policy.

Password-policy components

Requirement Policy attribute
Minimum time between changes pwdMinAge
Password expiration pwdMaxAge
Previous passwords retained pwdInHistory
Password syntax checking pwdCheckQuality
Minimum password length pwdMinLength
Expiration warning pwdExpireWarning
Grace-login count pwdGraceAuthnLimit
Grace-login time window pwdGraceExpiry
Enable failed-bind lockout pwdLockout
Lockout duration pwdLockoutDuration
Failed attempts before lockout pwdMaxFailure
Failure counter interval pwdFailureCountInterval
Maximum failed-bind timestamps retained pwdMaxRecordedFailure
Force change after admin reset pwdMustChange
Require old password for change pwdSafeModify
Delay after failed bind pwdMinDelay
Maximum delay between binds pwdMaxDelay
Maximum period without successful authentication pwdMaxIdle

All time-based policy values are stored in seconds. pwdMaxRecordedFailure limits how many failed-bind timestamps are retained on the entry; it is distinct from pwdMaxFailure, which determines when the account locks.

OpenLDAP also provides advanced attributes such as pwdMinDelay, pwdMaxDelay, and pwdMaxIdle. They are not enabled in this guide. pwdMinDelay and pwdMaxDelay implement an increasing temporary lockout after failed authentication attempts rather than delaying the bind response itself. pwdMaxIdle can lock an account after an extended period without successful authentication, but it requires last-bind tracking to be enabled on the database with olcLastBind: TRUE.

Default and per-account policy selection in OpenLDAP 2.6

OpenLDAP 2.6 documents two policy-selection methods:

Method Where it is configured When it applies
olcPPolicyDefault On the ppolicy overlay entry in cn=config When the account has no pwdPolicySubentry value
pwdPolicySubentry On the user or service account entry When that account needs a different policy than the default

The effective policy for an account is either its pwdPolicySubentry value or the policy named by olcPPolicyDefault on the overlay.

NOTE
The OpenLDAP 2.7 development branch introduces newer scoped and ordered policy-rule functionality (ppolicy_rules, olcPPolicyScopedRule, and related object classes). Do not apply development-version rule examples to an OpenLDAP 2.6 server unless the installed schema explicitly provides those object classes.

Plan the Password Policy

Decide policy values before you write LDIF. The examples below are for lab testing and compliance-style demonstrations, not universal security mandates.

Modern rotation and lockout guidance

Current NIST digital-identity guidance in SP 800-63B-4 advises against mandatory periodic password changes unless there is evidence that a password has been compromised. It also recommends at least 15 characters when a password is used as a single authentication factor, and at least 8 characters when the password is used only as part of multi-factor authentication. Many modern deployments set pwdMaxAge: 0 and require changes after suspected compromise, administrator reset, or another risk event. Use a fixed rotation period only when your regulatory or organizational policy requires it.

Failed-bind lockout can protect against repeated guessing, but it can also let an attacker deliberately lock known accounts. The Password Policy draft explicitly identifies denial-of-service risk in account-lockout designs. Use a temporary lockout duration, monitoring, rate limiting at the application or network layer, and a documented unlock procedure. Test service and privileged accounts separately.

Example lab and compliance-style policy

Two profiles help separate everyday policy from expiration testing:

Profile pwdMaxAge Purpose
Modern general baseline 0 No forced periodic rotation
Expiration demonstration 7776000 (90 days) Test expiry, warnings, and grace authentication

The expiration demonstration profile uses these values:

Requirement Example value Seconds or count
Minimum password length 15 bytes 15
Maximum password age 90 days 7776000
Expiration warning 14 days 1209600
Password history 5 passwords 5
Failed binds before lockout 5 5
Failure-count window 15 minutes 900
Temporary lockout 15 minutes 900
Maximum recorded failures 5 5
Grace logins after expiry 2 2
Grace-login validity 7 days 604800
Force change after admin reset Yes TRUE
Minimum time between changes None in baseline 0
Require old password in change request No in baseline FALSE
text
pwdAttribute: userPassword
pwdCheckQuality: 2
pwdMinLength: 15
pwdMinAge: 0
pwdMaxAge: 7776000
pwdExpireWarning: 1209600
pwdInHistory: 5
pwdLockout: TRUE
pwdMaxFailure: 5
pwdMaxRecordedFailure: 5
pwdFailureCountInterval: 900
pwdLockoutDuration: 900
pwdGraceAuthnLimit: 2
pwdGraceExpiry: 604800
pwdMustChange: TRUE
pwdSafeModify: FALSE

pwdMaxAge: 7776000 is included to demonstrate expiration, warnings, and grace authentication. It is not a universal security recommendation.

Interactions worth planning for:

  • pwdMaxAge: 0 means passwords do not expire.
  • pwdLockoutDuration: 0 creates a lock that remains until an administrator intervenes.
  • A temporary lockout is safer for the first implementation than an indefinite lock.
  • pwdGraceAuthnLimit: 0 blocks authentication immediately after expiry.
  • pwdExpireWarning is useful only when the client requests and displays the Password Policy Request control.
  • pwdSafeModify: TRUE can break clients that do not include the old password in the Password Modify request.

pwdMinLength measures the password in bytes. It does not by itself enforce uppercase, lowercase, digits, special characters, or dictionary checks. Full quality checking requires a compatible external check_password() module, covered briefly under Operate and Monitor ppolicy.

This example uses a 15-byte minimum to align more closely with current NIST guidance for single-factor passwords. OpenLDAP counts bytes rather than Unicode characters, so pwdMinLength alone does not provide exact character-based NIST compliance for non-ASCII passwords.


Verify the Package, Module and Schema

On Rocky Linux 10.2, the EPEL openldap-servers package ships ppolicy.so and check_password.so. It does not ship a standalone /etc/openldap/schema/ppolicy.ldif file like some older distributions.

Confirm the packaged module paths:

bash
rpm -ql openldap-servers | grep -E '/(ppolicy|check_password)(\.so|\.conf)'

Sample output:

output
/etc/openldap/check_password.conf
/usr/lib64/openldap/check_password.so
/usr/lib64/openldap/check_password.so.1.1
/usr/lib64/openldap/ppolicy.so
/usr/lib64/openldap/ppolicy.so.2
/usr/lib64/openldap/ppolicy.so.2.0.200

Check whether ppolicy is already loaded:

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

When ppolicy.so is not loaded, list all module entries and identify the DN whose olcModulePath matches the package's module directory:

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

Loading ppolicy.so registers the pwdPolicy schema dynamically. Verify policy attributes in cn=schema,cn=config:

bash
sudo ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b cn=schema,cn=config -s base '(objectClass=*)' olcObjectClasses olcAttributeTypes | grep -E 'pwdPolicy|pwdMaxAge|pwdAccountLockedTime'

The pwdPolicy auxiliary object class should appear after the module loads.

Confirm the conventional schema file is absent before copying old documentation:

bash
ls -l /etc/openldap/schema/ppolicy.ldif

Sample output on Rocky Linux 10.2:

output
ls: cannot access '/etc/openldap/schema/ppolicy.ldif': No such file or directory

Do not blindly run ldapadd -f /etc/openldap/schema/ppolicy.ldif unless that file exists on your host.

NOTE
The LDAP Password Policy specification remains an Internet-Draft. Functionality is mature in OpenLDAP, but describe it as draft-based behavior rather than RFC-mandated semantics.

Back Up and Discover the MDB Database

Back up cn=config before loading modules, adding overlays, or changing policy assignment. See OpenLDAP backup and restore for recurring backup design.

Become root for protected backup paths, or save exports into a directory your current user can write:

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

Export the current dynamic configuration:

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

Shell redirection to /root requires root privileges even when ldapsearch itself runs as an LDAP identity. sudo commonly starts with a sanitized environment, so set LDAPTLS_CACERT inside the root shell:

bash
sudo sh -c '
LDAPTLS_CACERT=/etc/openldap/certs/example-ldap-ca.crt \
ldapsearch -x -ZZ \
  -H ldap://ldap-server.example.com \
  -D "cn=admin,dc=example,dc=com" -W \
  -b "uid=jdoe,ou=people,dc=example,dc=com" \
  -s base -LLL "*" "+" \
  > /root/openldap-ppolicy-backup/jdoe-before.ldif
'

Enter the directory administrator password at the -W prompt inside the root shell.

Discover the MDB database DN dynamically:

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}')

Fail safely when the suffix is missing:

bash
if [[ -z "$MDB_DN" ]]; then echo "MDB database for dc=example,dc=com was not found." >&2; exit 1; fi
bash
printf 'MDB database DN: %s\n' "$MDB_DN"

Sample output:

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

Database index numbers vary by host. Always discover MDB_DN on the system you are changing.


Load the ppolicy Module and Add the Overlay

Module and overlay changes use the local ldapi:/// socket with SASL EXTERNAL. See OpenLDAP cn=config and MDB explained and the ldapmodify cheat sheet.

If ppolicy.so is not yet listed, identify the module-list entry from the verification step in the previous section. On the lab host the entry was cn=module{0},cn=config; set MODULE_DN to the exact DN whose olcModulePath matches the package module directory you verified:

bash
MODULE_DN='cn=module{0},cn=config'

Alternatively, filter by module path:

bash
MODULE_DN=$(sudo ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b cn=config '(&(objectClass=olcModuleList)(olcModulePath=/usr/lib64/openldap))' dn | awk '/^dn: / {sub(/^dn: /, ""); print; exit}')
printf 'Module list DN: %s\n' "$MODULE_DN"

Generate load-ppolicy-module.ldif from the verified MODULE_DN:

bash
cat > load-ppolicy-module.ldif <<EOF
dn: ${MODULE_DN}
changetype: modify
add: olcModuleLoad
olcModuleLoad: ppolicy.so
EOF

Apply the module load:

bash
sudo ldapmodify -Q -Y EXTERNAL -H ldapi:/// \
  -f load-ppolicy-module.ldif

Add the overlay without setting a default policy yet. Generate the LDIF from the discovered MDB_DN:

bash
cat > add-ppolicy-overlay.ldif <<EOF
dn: olcOverlay=ppolicy,${MDB_DN}
objectClass: olcOverlayConfig
objectClass: olcPPolicyConfig
olcOverlay: ppolicy
EOF
bash
sudo ldapadd -Q -Y EXTERNAL -H ldapi:/// -f add-ppolicy-overlay.ldif

Discover the numbered overlay DN OpenLDAP assigned:

bash
PPOLICY_DN=$(sudo ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b "$MDB_DN" '(olcOverlay=ppolicy)' dn | awk '/^dn: / {sub(/^dn: /, ""); print; exit}')
printf 'ppolicy overlay DN: %s\n' "$PPOLICY_DN"

Sample output:

output
ppolicy overlay DN: olcOverlay={3}ppolicy,olcDatabase={2}mdb,cn=config

Overlay index numbers vary by host, like MDB database indices.


Create and Assign the Default Policy

Policy entries live in the application suffix, not in cn=config.

text
dc=example,dc=com
└── ou=policies
    ├── cn=default
    ├── cn=service-accounts
    └── cn=privileged-users

Create the policy entry

pwdPolicy is an auxiliary object class, so the entry also requires a structural class. Some installations load the optional namedPolicy schema; upstream OpenLDAP 2.6 still describes namedPolicy as a suitable structural class when that schema is present. This tested Rocky Linux configuration did not have namedPolicy loaded after enabling ppolicy.so, so the guide uses the standard organizationalRole structural class instead.

Create default-password-policy.ldif:

text
dn: ou=policies,dc=example,dc=com
objectClass: top
objectClass: organizationalUnit
ou: policies

dn: cn=default,ou=policies,dc=example,dc=com
objectClass: top
objectClass: organizationalRole
objectClass: pwdPolicy
cn: default
pwdAttribute: userPassword
pwdCheckQuality: 2
pwdMinLength: 15
pwdMinAge: 0
pwdMaxAge: 7776000
pwdExpireWarning: 1209600
pwdInHistory: 5
pwdLockout: TRUE
pwdMaxFailure: 5
pwdMaxRecordedFailure: 5
pwdFailureCountInterval: 900
pwdLockoutDuration: 900
pwdGraceAuthnLimit: 2
pwdGraceExpiry: 604800
pwdMustChange: TRUE
pwdSafeModify: FALSE

Add the policy data over StartTLS:

bash
ldapadd -x -ZZ -H ldap://ldap-server.example.com -D "cn=admin,dc=example,dc=com" -W -f default-password-policy.ldif

Configure olcPPolicyDefault

Assign the default policy on the overlay entry using the discovered PPOLICY_DN:

bash
cat > set-ppolicy-default.ldif <<EOF
dn: ${PPOLICY_DN}
changetype: modify
add: olcPPolicyDefault
olcPPolicyDefault: cn=default,ou=policies,dc=example,dc=com
EOF
bash
sudo ldapmodify -Q -Y EXTERNAL -H ldapi:/// -f set-ppolicy-default.ldif

Verify the assignment:

bash
sudo ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b "$PPOLICY_DN" -s base olcOverlay olcPPolicyDefault

Sample output:

output
dn: olcOverlay={3}ppolicy,olcDatabase={2}mdb,cn=config
olcOverlay: {3}ppolicy
olcPPolicyDefault: cn=default,ou=policies,dc=example,dc=com

Accounts without pwdPolicySubentry now inherit cn=default,ou=policies,dc=example,dc=com.

Assign a different policy with pwdPolicySubentry

Create additional pwdPolicy entries when service accounts or privileged users need different limits. For example, add cn=service-accounts,ou=policies,dc=example,dc=com with a longer pwdMaxAge or pwdMaxAge: 0 for non-expiring service passwords.

Point one account at the alternate policy. Save assign-service-account-policy.ldif:

text
dn: uid=svcbackup,ou=people,dc=example,dc=com
changetype: modify
add: pwdPolicySubentry
pwdPolicySubentry: cn=service-accounts,ou=policies,dc=example,dc=com

pwdPolicySubentry is an operational attribute. The delegated password administrator ACL in this guide grants it only read, which is appropriate for monitoring, but that identity cannot assign the override. Use the database administrator or another identity explicitly granted sufficient management access. OpenLDAP uses this attribute as the direct per-account policy pointer, falling back to the configured default when it is absent.

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

Verify the override:

bash
ldapsearch -x -ZZ \
  -H ldap://ldap-server.example.com \
  -D "cn=admin,dc=example,dc=com" -W \
  -b "uid=svcbackup,ou=people,dc=example,dc=com" \
  -s base -LLL pwdPolicySubentry pwdChangedTime

Sample output:

output
dn: uid=svcbackup,ou=people,dc=example,dc=com
pwdChangedTime: 20260714135354Z
pwdPolicySubentry: cn=service-accounts,ou=policies,dc=example,dc=com

Using the database administrator here is appropriate for assigning directory policy metadata. The root-DN caveat applies to testing password history, quality, and reset behavior—not to assigning pwdPolicySubentry.

Per-entry assignment is supported in OpenLDAP 2.6, but administrators must maintain pwdPolicySubentry during account creation, migration, and auditing. Document which accounts override the default and review those attributes during access reviews.


Configure Password-Policy ACLs

ACL design belongs in OpenLDAP ACL configuration with practical examples. This section lists the minimum identities password policy depends on.

Identity Intended use
Database root DN Emergency recovery and configuration
Delegated password administrator Password resets, unlocks, forced-change workflow
User DN Normal password change and history testing

Normal user

Recommended direction for userPassword:

text
to attrs=userPassword
    by self =xw
    by anonymous auth
    by * none

Delegated password administrator

Create a dedicated account such as uid=pwdadmin,ou=system,dc=example,dc=com. ACL permissions apply to every attribute named in a to attrs= clause, so do not combine userPassword with lockout or reset attributes in the same self-service rule. A user granted by self =xw on that combined target could modify their own pwdAccountLockedTime or pwdReset.

Use separate ACL examples:

text
to attrs=userPassword
    by dn.exact="uid=pwdadmin,ou=system,dc=example,dc=com" manage
    by self =xw
    by anonymous auth
    by * none

to attrs=pwdAccountLockedTime,pwdReset
    by dn.exact="uid=pwdadmin,ou=system,dc=example,dc=com" manage
    by * none

to attrs=pwdFailureTime,pwdChangedTime,pwdGraceUseTime,pwdPolicySubentry
    by dn.exact="uid=pwdadmin,ou=system,dc=example,dc=com" read
    by * none

to dn.subtree="ou=people,dc=example,dc=com" attrs=entry,uid
    by dn.exact="uid=pwdadmin,ou=system,dc=example,dc=com" read
    by * break

Keep pwdHistory protected. A delegated reset administrator normally does not need to read its stored password representations.

Apply the split ACL examples—or equivalent rules from OpenLDAP ACL configuration with practical examples—before testing uid=pwdadmin commands later in this guide. The final rule gives the delegated administrator enough access to locate user entries and read uid during monitoring searches. Password reset, lockout management, and operational-attribute access remain controlled by the preceding attribute-specific rules.

The exact ordered olcAccess policy remains in the ACL guide. The ppolicy overlay treats manage on userPassword as a password-administrator identity for pwdMustChange and pwdReset handling.

Root-DN caveat

Operations performed as cn=admin,dc=example,dc=com when that DN is the database rootDN can bypass password-history enforcement and may not set pwdReset the way a delegated password administrator does. Use the root DN only for break-glass recovery. Test policy behavior with uid=jdoe or uid=pwdadmin.


Initialize Existing Accounts

The overlay uses pwdChangedTime to calculate password age. If a user already has userPassword but no pwdChangedTime, that password does not expire merely because pwdMaxAge was enabled.

Inspect the test user:

bash
ldapsearch -x -ZZ -H ldap://ldap-server.example.com -D "cn=admin,dc=example,dc=com" -W -b "uid=jdoe,ou=people,dc=example,dc=com" -s base -LLL pwdChangedTime pwdPolicySubentry

Sample output when the account inherits the default policy:

output
dn: uid=jdoe,ou=people,dc=example,dc=com
pwdChangedTime: 20260714135332Z

Initialize policy state through a controlled password change as the user:

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

-A securely prompts for the old password, while -S prompts for the new password instead of exposing either value in shell history. The Password Modify extended operation lets slapd inspect and hash the cleartext password over the encrypted StartTLS session.

Because the user is also the bind identity, the current password may be requested once for the bind and again as the old password in the Password Modify operation. With pwdSafeModify: FALSE, supplying -A is optional, but it is retained here to test an explicit old-password workflow. The OpenLDAP command documentation distinguishes -W (bind password), -A (old password in the Password Modify request), and -S (new password).

Do not initialize users by manually fabricating pwdChangedTime in LDIF. For a large existing directory, plan user communication and a staged reset process before enabling a short pwdMaxAge.


Test the Policy

Test enforcement using the user's own identity or the delegated password administrator. The database root DN is not a reliable policy test bind.

Length and history

Attempt a password shorter than pwdMinLength:

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

Sample output:

output
Result: Constraint violation (19)
Additional info: Password fails quality checking policy

Cycle through several passwords as the user, then attempt to reuse an older value. When history blocks reuse:

output
Result: Constraint violation (19)
Additional info: Password is in history of old passwords

With pwdCheckQuality: 2, the server rejects passwords it cannot inspect. Use Password Modify through ldappasswd, not pre-hashed values from slappasswd in an ldapmodify change.

Resetting an old password as the database root DN may succeed even when the same password is rejected during a normal user-initiated change. Document both results so future validation uses the identity your users actually employ.

Expiration and warnings

In a disposable lab, temporarily lower pwdMaxAge and pwdExpireWarning on a test policy entry—for example 300 and 180 seconds—then restore production values after testing.

Request the password-policy control:

bash
ldapsearch -x -ZZ -e ppolicy -H ldap://ldap-server.example.com -D "uid=jdoe,ou=people,dc=example,dc=com" -W -b "uid=jdoe,ou=people,dc=example,dc=com" -s base -LLL uid

The server sends an expiration warning only when the client requests the Password Policy Request control.

Grace authentication

After expiry, bind with -e ppolicy using ldapwhoami, record grace-login information from the policy response, and query pwdGraceUseTime with an administrative identity. Each successful expired-password bind consumes a grace login until the configured limit is exhausted.

Failed-bind lockout

Perform several intentionally incorrect binds as the user:

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

After enough failures, inspect policy state:

bash
ldapsearch -x -ZZ -H ldap://ldap-server.example.com -D "uid=pwdadmin,ou=system,dc=example,dc=com" -W -b "uid=jdoe,ou=people,dc=example,dc=com" -s base -LLL pwdFailureTime pwdAccountLockedTime

Sample output after five failed binds in the lab:

output
dn: uid=jdoe,ou=people,dc=example,dc=com
pwdFailureTime: 20260714135709.919486Z
pwdFailureTime: 20260714135709.951901Z
pwdFailureTime: 20260714135709.982220Z
pwdFailureTime: 20260714135710.042172Z
pwdFailureTime: 20260714135710.096818Z
pwdAccountLockedTime: 20260714135710Z

While locked, even the correct password returns ldap_bind: Invalid credentials (49) unless you deliberately enable explicit lockout responses on the overlay.

Forced change after administrator reset

With pwdMustChange: TRUE, reset the password with the delegated administrator:

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

Verify reset state:

bash
ldapsearch -x -ZZ -H ldap://ldap-server.example.com -D "uid=pwdadmin,ou=system,dc=example,dc=com" -W -b "uid=jdoe,ou=people,dc=example,dc=com" -s base -LLL pwdReset pwdChangedTime

Sample output:

output
dn: uid=jdoe,ou=people,dc=example,dc=com
pwdChangedTime: 20260714135710Z
pwdReset: TRUE

The user should change the temporary password with ldappasswd -A -S through a compatible client. Test client support on SSSD OpenLDAP clients.


Operate and Monitor ppolicy

Unlock and administratively disable accounts

Remove the lock timestamp with the delegated password administrator:

text
dn: uid=jdoe,ou=people,dc=example,dc=com
changetype: modify
delete: pwdAccountLockedTime
bash
ldapmodify -x -ZZ -H ldap://ldap-server.example.com -D "uid=pwdadmin,ou=system,dc=example,dc=com" -W -f unlock-jdoe.ldif

Sample output:

output
modifying entry "uid=jdoe,ou=people,dc=example,dc=com"

After deleting pwdAccountLockedTime, inspect the remaining pwdFailureTime values before returning the account to service. Old failures normally age out according to pwdFailureCountInterval, but a remaining near-threshold failure state can cause the account to lock again after another failed bind. Test the unlock against the same replica or load-balanced endpoint users normally authenticate through.

Attempting to delete pwdFailureTime directly often fails:

output
ldap_modify: Constraint violation (19)
additional info: pwdFailureTime: no user modification allowed

For permanent administrative disable, OpenLDAP recognizes:

text
pwdAccountLockedTime: 000001010000Z

The permanent-lock sentinel is enforced only when the account's effective password policy has pwdLockout: TRUE.

Use that sentinel only with a documented unlock process.

Condition Controlled by
Password too old pwdMaxAge
Too many failed binds pwdLockout, pwdMaxFailure
Administrative permanent lock pwdAccountLockedTime sentinel value
Account valid only within dates pwdStartTime, pwdEndTime

Search operational attributes

Request operational attributes explicitly. A normal * search does not return them to unprivileged clients.

bash
ldapsearch -x -ZZ -H ldap://ldap-server.example.com -D "uid=pwdadmin,ou=system,dc=example,dc=com" -W -b "ou=people,dc=example,dc=com" -LLL "(pwdAccountLockedTime=*)" dn uid pwdAccountLockedTime
Operational attribute Meaning
pwdPolicySubentry Per-account policy override DN; it may be absent when the account inherits olcPPolicyDefault
pwdChangedTime Last server-recorded password change
pwdFailureTime Failed-bind timestamps
pwdAccountLockedTime Account lock timestamp
pwdHistory Previously used password values
pwdGraceUseTime Expired-password grace logins consumed
pwdReset Administrator reset requires user change

OpenLDAP 2.6 selects the per-entry policy when pwdPolicySubentry exists and otherwise uses the overlay's default policy.

Administrative monitoring searches for locked accounts, accounts with pwdFailureTime, and accounts with pwdReset=TRUE should run over StartTLS and remain restricted to delegated operators.

SSSD behavior

The OpenLDAP SSSD client guide owns complete NSS, PAM, and SSH setup. On current SSSD releases, these defaults already match the intended password-policy workflow:

  • ldap_pwmodify_mode defaults to exop
  • ldap_use_ppolicy defaults to true
  • ldap_pwd_policy defaults to none, with server-side ppolicy taking precedence when enabled

Set them explicitly in sssd.conf to document intent:

ini
auth_provider = ldap
chpass_provider = ldap
ldap_uri = ldap://ldap-server.example.com
ldap_id_use_start_tls = true

# Current defaults, but explicit here for review
ldap_pwmodify_mode = exop
ldap_use_ppolicy = true

When grace logins are enabled, consider:

ini
ldap_ppolicy_pwd_change_threshold = 2

A value of 2 is the practical minimum because password-change processing may consume additional grace authentications.

Server condition Direct bind SSSD/PAM login
Password valid Succeeds Login succeeds
Password nearing expiry Warning only with ppolicy control Warning depends on PAM/application display
Password expired with grace remaining Bind may succeed with policy response Client may prompt for password change
Grace logins exhausted Bind fails Login fails or password-change flow may fail
Account temporarily locked Invalid credentials Login denied
Administrator reset with pwdMustChange Policy response requires change Compatible PAM stack prompts for a new password
Client offline Server is not contacted Cached authentication behavior applies

SSSD can cache credentials for offline authentication. A newly imposed server-side lockout or expiration cannot be evaluated while the client is disconnected from the LDAP server.

Replication caveats

Password-policy operational attributes live on user entries. The draft does not fully define their behavior in replicated deployments. A failed authentication attempt against a consumer may update only that consumer's local state, while provider-originated changes may later overwrite it.

The overlay provides ppolicy_forward_updates for forwarding bind-generated policy state from a consumer to a provider; that path requires additional updateref and chain-overlay configuration documented in OpenLDAP provider-consumer replication. Multi-provider designs should account for lockout state recorded on different nodes and for load balancers distributing attempts across providers. See OpenLDAP multi-provider replication.

Include policy entries and overlay settings in OpenLDAP backup and restore.

Optional stronger password quality

The EPEL package includes check_password.so, but external password-quality configuration is module- and package-specific. This guide tests only the built-in pwdMinLength behavior. Configure and validate olcPPolicyCheckModule, pwdPolicyChecker, and pwdUseCheckModule separately before enabling it in production.


Troubleshooting

Error or symptom Likely cause Recommended action
Policy entry cannot be added pwdPolicy schema unavailable or structural class missing Verify ppolicy.so is loaded; use organizationalRole + pwdPolicy when optional namedPolicy schema is not installed
ppolicy.so cannot be loaded Wrong module path or package missing Run rpm -ql openldap-servers and confirm olcModulePath
Policy exists but is not enforced No olcPPolicyDefault and no pwdPolicySubentry on the account Verify overlay assignment and per-entry overrides
Password never expires User lacks pwdChangedTime Perform a controlled user password change to initialize aging
Short password is accepted Test used root DN, quality checking disabled, or password was pre-hashed Test with a normal identity and pwdCheckQuality: 2 through ldappasswd -A -S
Pre-hashed password is rejected Server cannot inspect hash at quality level 2 Use Password Modify exop over TLS
Password history appears ineffective Password was reset by root DN Test with the user or delegated password administrator
Account does not lock pwdLockout is false or binds hit different DNs/providers Inspect policy values and pwdFailureTime
Account locks but user sees only invalid credentials Expected information-hiding behavior Request ppolicy control for administrative testing
Account locks again immediately after unlock Stale pwdFailureTime values remain near threshold Inspect failure timestamps and pwdFailureCountInterval; test on the same replica users normally use
Lockout never clears pwdLockoutDuration is zero Unlock administratively or set a temporary duration
Grace warning is not displayed Client did not request or render the ppolicy control Test with -e ppolicy and SSSD/PAM
pwdReset does not appear Reset used root DN or insufficient ACL level Use uid=pwdadmin or another delegated password administrator
SSSD login fails with access rules ldap_pwdlockout_dn misconfigured for access_provider Check ldap_pwdlockout_dn only when the domain uses access_provider = ldap with a ppolicy or lockout access rule; it is separate from normal server-side bind enforcement
Lockout differs between replicas Operational state updated independently on each node Review topology and ppolicy_forward_updates
Policy works online but offline login succeeds SSSD is using cached credentials Review offline credential policy and cache expiry

Useful diagnostics:

bash
sudo ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b "$PPOLICY_DN" -s base olcOverlay olcPPolicyDefault

Confirms the ppolicy overlay is loaded and which policy DN is the default.

bash
sudo slaptest -u

Validates configuration after overlay or policy attribute changes.

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

Shows bind failures, lockout events, and ppolicy responses on the server.

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

Use on the client when LDAP policy works but SSSD login or grace warnings fail.


References


Summary

You can enforce OpenLDAP password expiration, history, length, lockout, grace logins, and forced resets on OpenLDAP 2.6 by loading ppolicy.so, attaching the overlay to the discovered MDB database, creating pwdPolicy entries, assigning the default with olcPPolicyDefault, and overriding individual accounts with pwdPolicySubentry when needed.

Initialize existing users with a real password change so pwdChangedTime is recorded, test enforcement with normal user identities or a delegated password administrator rather than the database root DN, protect password operations with StartTLS, align ACLs with overlay operational attributes, and validate SSSD and replication behavior before relying on lockout or expiry in production.


Frequently Asked Questions

1. What is the OpenLDAP ppolicy overlay?

The ppolicy overlay is an OpenLDAP module that enforces password aging, history, length, lockout, grace logins, and forced password changes on entries that carry userPassword beneath a configured MDB database.

2. Is OpenLDAP password policy based on an RFC?

No. ppolicy implements the LDAP Password Policy Internet-Draft, which was never finalized as an RFC; treat it as a widely deployed draft specification rather than an IETF standard.

3. Does OpenLDAP enforce password expiration by default?

No. Expiration, history, and lockout apply only after you load ppolicy.so, attach the overlay, create a pwdPolicy entry, and assign it with olcPPolicyDefault or pwdPolicySubentry on the account.

4. Why does an existing user password not expire after enabling pwdMaxAge?

The overlay calculates age from pwdChangedTime; accounts that already had userPassword before policy was enabled keep a non-expiring password until the user performs a successful password change that records pwdChangedTime.

5. What is the difference between pwdMaxAge and pwdExpireWarning?

pwdMaxAge sets the maximum password lifetime in seconds, while pwdExpireWarning defines how many seconds before expiry the server may return a warning when the client requests the Password Policy Request control.

6. How do OpenLDAP grace logins work?

After expiry, pwdGraceAuthnLimit allows a limited number of successful binds within pwdGraceExpiry so the user can reach a password-change workflow before authentication is fully blocked.

7. How do I prevent users from reusing old passwords?

Set pwdInHistory on the pwdPolicy entry and test changes using the user's own identity through ldappasswd -A -S; root-DN resets can bypass history checks.

8. Why is pwdMinLength not enforcing password complexity?

pwdMinLength counts password bytes only; it does not require mixed case, digits, symbols, or dictionary checks unless you add a compatible external check_password module.

9. Why is a pre-hashed password rejected when pwdCheckQuality is 2?

Quality level 2 requires the server to inspect cleartext during the Password Modify extended operation, so client-supplied hashes from slappasswd or ldapmodify commonly fail policy validation.

10. How do I lock an OpenLDAP account after failed logins?

Enable pwdLockout, set pwdMaxFailure and pwdFailureCountInterval on the policy entry, then failed binds against the user DN accumulate pwdFailureTime until pwdAccountLockedTime is written.

11. How do I unlock an OpenLDAP user?

A delegated administrator with manage access deletes pwdAccountLockedTime, then inspects remaining pwdFailureTime values before returning the account to service; failure timestamps are operational and usually cannot be deleted directly.

12. Why does a locked user receive only Invalid credentials?

By default the overlay hides lockout details from bind clients; only a client requesting the ppolicy control or an administrative search of operational attributes reveals the locked state.

13. How do I force a password change after an administrator reset?

Set pwdMustChange TRUE on the policy and reset the password with a delegated password administrator who has manage on userPassword; the server sets pwdReset TRUE until the user changes the password through a compatible Password Modify client.

14. Why does password-policy testing work differently as the root DN?

Operations performed as the database rootDN bypass parts of ppolicy such as password-history enforcement, so length, history, and reset behavior must be tested with normal user or delegated-administrator identities.

15. What is pwdPolicySubentry used for in OpenLDAP 2.6?

pwdPolicySubentry selects a non-default pwdPolicy entry for one account; when it is absent, the overlay uses olcPPolicyDefault configured on the ppolicy overlay entry.

16. How do I assign multiple password policies in OpenLDAP 2.6?

Create multiple pwdPolicy entries, set olcPPolicyDefault for the general population, and add pwdPolicySubentry on accounts that need a different policy such as service accounts or privileged users.

17. Does SSSD display OpenLDAP password-expiration warnings?

SSSD can request server-side ppolicy controls when ldap_use_ppolicy is true, but the warning appears only if the PAM application displays policy responses returned during authentication or password change.

18. Can an OpenLDAP user still log in offline through SSSD after being locked?

Yes. Cached credentials can allow offline login until the cache expires or the client reconnects to LDAP, so server-side lockout does not instantly invalidate every cached session.

19. How does password lockout work with OpenLDAP replication?

Operational attributes such as pwdFailureTime and pwdAccountLockedTime replicate as entry data, but failed binds on different replicas can produce inconsistent lockout state unless you design forwarding and authentication routing carefully.

20. Do I need TLS when using the ppolicy overlay?

Yes for production. Password Modify and bind operations send secrets to the server; use StartTLS or LDAPS as documented in the TLS guide before enabling policy in any network beyond a closed lab.
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 …