Applying password policies to OpenLDAP with the ppolicy overlay: pwdMinLength, pwdMaxLength, pwdMaxAge, pwdInHistory, account lockout, grace logins, SSHA password hashing, and external cracklib validation.

In episode 11 you learned about overlays as OpenLDAP's modular mechanism, including a glimpse of ppolicy and syncprov. Episode 12 turns that concept into real practice: a complete password policy that governs length, age, history, and account lockout. This is one of the features most often requested once an organization starts treating LDAP as the source of truth for authentication — because without a policy, userPassword is just a password with no rules.
ppolicy is the overlay that brings password policy attributes into the directory. The module enforces quality, age, history, and lockout rules on every bind and password change operation, without changing a single line of client application code.
Quality requirements are measured through pwdCheckQuality and pwdMinLength: the quality level determines how strictly new passwords are tested, while the minimum length serves as the security baseline. Policy application is done per-entry via pwdPolicySubentry, or globally via the default policy mounted in the overlay configuration.
dn: cn=module{0},cn=config
objectClass: olcModuleList
olcModulePath: /usr/lib/ldap
olcModuleLoad: ppolicy.laOn Debian and Ubuntu, overlay modules live in /usr/lib/ldap and are loaded with olcModuleLoad: ppolicy.la. If you recompile a module (for example after building from source), don't forget to run slaptest -u to make sure the configuration is valid.
A policy is defined as an entry with the pwdPolicy object class. Its attributes work in pairs to form a complete rule set:
| Attribute | Function |
|---|---|
pwdMinLength | Minimum password length |
pwdMaxLength | Maximum password length |
pwdMinAge | Minimum age before a password may be changed (seconds) |
pwdMaxAge | Maximum age before a password expires (seconds) |
pwdInHistory | Number of old passwords that can't be reused |
pwdCheckQuality | Quality checking level (0, 1, or 2) |
pwdMaxFailure | Bind failure limit before the account locks |
pwdLockout | Enable or disable account lockout |
pwdLockoutDuration | Lockout duration in seconds |
pwdGraceAuthNLimit | Number of grace logins after a password expires |
Two operational attributes accompany this mechanism: pwdChangedTime records when the password was last changed, and pwdFailureTime records each bind failure time — both are filled automatically by the overlay, no manual setup needed. pwdMaxAge and pwdMinAge are in seconds, so 90 days equals 7776000.
A policy entry is created like any other entry, but with the pwdPolicy and person object classes (for the cn and sn attributes):
dn: cn=default,ou=policies,dc=example,dc=com
objectClass: pwdPolicy
objectClass: person
cn: default
sn: default policy
pwdAttribute: userPassword
pwdMinLength: 12
pwdMaxLength: 64
pwdMinAge: 1
pwdMaxAge: 7776000
pwdInHistory: 5
pwdCheckQuality: 2
pwdMaxFailure: 5
pwdLockout: TRUE
pwdLockoutDuration: 900
pwdGraceAuthNLimit: 3Add it with ldapadd -x -D cn=admin,dc=example,dc=com -W from this LDIF file. pwdCheckQuality: 2 means quality is checked when the password is set and on the first bind; pwdLockout: TRUE with pwdMaxFailure: 5 and pwdLockoutDuration: 900 locks an account for 15 minutes after five failures.
So the overlay knows which policy is the default, set olcPPolicyDefault in the overlay configuration in cn=config:
dn: olcOverlay=ppolicy,olcDatabase={1}mdb,cn=config
objectClass: olcOverlayConfig
objectClass: olcPPolicyConfig
olcOverlay: ppolicy
olcPPolicyDefault: cn=default,ou=policies,dc=example,dc=com
olcPPolicyHashCleartext: FALSE
olcPPolicyUseLockout: TRUEThe effective policy follows these rules:
pwdPolicySubentry, that policy applies (per-user policy).olcPPolicyDefault is the fallback for the entire database.A per-user policy is mounted directly on the person's entry — for example, pwdPolicySubentry: cn=strict,ou=policies,dc=example,dc=com added to uid=budi,ou=people,dc=example,dc=com. This is useful for sensitive accounts like admins or service accounts that need stricter rules.
How a password is stored in userPassword determines how much effort an attacker needs to reverse it. OpenLDAP stores password values together with a hash scheme label:
| Scheme | Strength | Notes |
|---|---|---|
{SSHA} | Moderate | Salted SHA-1, the old default standard |
{SSHA256} | Good | Salted SHA-256 |
{SSHA512} | Good | Salted SHA-512, the modern recommendation |
{CRYPT} | Depends | Uses the system crypt, usable with shadow |
{MD5} | Weak | Unsalted, deprecated |
{ARGON2} | Strong | Argon2, modern, requires slapd build support |
The default hash is set in cn=config with olcPasswordHash. For a more secure value than the built-in {SSHA}, change it to {SSHA512}:
dn: cn=config
changetype: modify
replace: olcPasswordHash
olcPasswordHash: {SSHA512}Keep in mind: changing the default only affects passwords hashed afterward. Existing passwords keep their old scheme until the user changes the password.
pwdMinLength and pwdCheckQuality only check basic form. For smarter rules — for example, forbidding dictionary words or keyboard patterns — ppolicy supports external validation through pwdCheckModule on the policy entry:
dn: cn=default,ou=policies,dc=example,dc=com
changetype: modify
replace: pwdCheckModule
pwdCheckModule: check_password.soThis module can integrate cracklib, the same library as the cracklib-unix used by pam_cracklib on Linux systems, or a custom validator you write yourself and compile as a shared library. On Debian and Ubuntu, make sure the module's supporting packages are installed and the module sits in a directory slapd can read. This layer is optional — many deployments stay with pwdCheckQuality for simplicity.
Each part of the policy must be tested separately, and all the tests can be done with standard clients:
ldapmodify -x -D uid=budi,ou=people,dc=example,dc=com -W
ldapwhoami -x -D uid=budi,ou=people,dc=example,dc=com -w wrong
ldapsearch -x -D cn=admin,dc=example,dc=com -W \
-b uid=budi,ou=people,dc=example,dc=com pwdAccountLockedTimepwdChangedTime updates.pwdAccountLockedTime appears.pwdMaxAge, wait for it to pass, then bind; the server rejects with result code 49 and the message password expired.pwdGraceAuthNLimit times, and an expiration warning message appears in the bind result.Important
Watch out for one common trap: testing lockout against the admin account. If cn=admin follows the default policy and gets locked, the main gateway into the directory locks too. In the lab, give admin a pwdPolicySubentry pointing to a loose policy, or test lockout on a regular user account first.
In this episode 12 you applied the ppolicy overlay: loading the module, understanding the attributes from pwdMinLength to pwdGraceAuthNLimit, creating default and per-user policy entries, choosing hash schemes from {SSHA} to {ARGON2}, enabling external validation via pwdCheckModule, and testing password changes, lockout, expiration, and grace logins.
Key takeaways:
ppolicy.la, set olcPPolicyDefault, and create a pwdPolicy entry.pwdPolicySubentry beats the default policy, the default policy beats no policy.{SSHA512} and avoid the deprecated {MD5}.In the next episode, episode 13, we cover LDAP replication — how a single provider distributes its data to consumers with syncrepl, from refreshOnly to multi-master. The password policies you created here will be replicated too and must behave identically across all servers.