Disable, Reactivate, and Automatically Inactivate User Accounts in 389 Directory Server

Disable and reactivate LDAP user accounts in 389 Directory Server with dsidm account lock, nsAccountLock, role lock, and the Account Policy plug-in for lastLoginTime tracking and automatic inactivity enforcement.

Published

Updated

Read time 17 min read

Reviewed byDeepak Prasad

389 Directory Server account lifecycle with manual nsAccountLock, role lock, lastLoginTime tracking, and Account Policy plug-in inactivity

You sometimes need to stop a user from authenticating without deleting the LDAP entry. 389 Directory Server supports manual inactivation through nsAccountLock, role-based suspension, and automatic denial after a configured period of inactivity through the Account Policy plug-in.

In this guide, we'll check account state, lock and unlock individual users, apply nsAccountLock with LDIF, lock roles that contain multiple users, record lastLoginTime, configure accountInactivityLimit, test inactivity denial, reactivate blocked accounts, and audit disabled or inactive users. Failed-password lockout belongs in account lockout, not here.

Before you start:

IMPORTANT
This guide covers manual account inactivation, role lock, lastLoginTime tracking, and Account Policy inactivity. It does not configure failed-password lockout, password-expiration grace logins, or pass-through authentication.

Tested on: Rocky Linux 10.2; 389 Directory Server 3.2.0.

Account-lifecycle tests use instance ldap2 with suffix dc=lab,dc=example,dc=com, LDAP port 1389, and LDAPS port 1636. That keeps Account Policy plug-in work away from other chapters exercising ldap1. Test users are uid=lockout-test, uid=inactive-test, and uid=user1 under ou=people,dc=lab,dc=example,dc=com. Bind Directory Manager with -D "cn=Directory Manager" -y /root/ldap2-dm.pw when dsidm or dsconf prompts for a password on this instance.

Set the lab CA before LDAPS user binds and ldapsearch checks:

bash
export LDAPTLS_CACERT=/root/ldap2-ca-chain.pem

That variable tells OpenLDAP clients which CA certificate to trust for LDAPS on this instance.

Use ldaps://127.0.0.1:1636 for authentication tests in this guide. Do not copy plaintext ldap:// password binds to remote or production servers. The TLS chapter shows how to install a trusted CA; on this disposable loopback lab you may need LDAPTLS_REQCERT=allow when the instance uses a self-signed certificate.


Record the Account Policy baseline

Save the plug-in state before you change anything:

bash
dsconf -D "cn=Directory Manager" -y /root/ldap2-dm.pw ldap2 plugin account-policy status

That command reports whether the Account Policy plug-in is enabled on ldap2.

Sample output on the tested host before the exercise:

output
Plugin 'Account Policy Plugin' is disabled

I also read the plug-in configuration entry so I know whether login timestamps are being recorded:

bash
dsconf -D "cn=Directory Manager" -y /root/ldap2-dm.pw ldap2 plugin account-policy config-entry show "cn=config,cn=Account Policy Plugin,cn=plugins,cn=config"

Sample output excerpt:

output
alwaysrecordlogin: no

Also confirm whether lab users, the SuspendedUsers role, policy entries, or CoS definitions from an earlier run already exist before you create duplicates.


Understand the different account lock states

State Trigger Main mechanism
Manual account inactivation Administrator action nsAccountLock
Role-based inactivation Administrator locks a role Role membership
Failed-password lockout Repeated incorrect passwords Password-policy counters
Inactivity lockout No successful login within a time limit Account Policy plug-in
Account-age expiration Time since account creation Account Policy plug-in
Password-expiry inactivation Time after password expiration Account Policy plug-in

Manual inactivation writes or manages nsAccountLock. When that attribute is true, Directory Server rejects the user's bind. The Account Policy plug-in instead evaluates configured time attributes during authentication and denies access when the limit has been exceeded, often without setting nsAccountLock.

Failed-password lockout is documented in account lockout.


Check the current status of a user account

Inspect the administrative account state with dsidm account entry-status:

bash
dsidm -D "cn=Directory Manager" -y /root/ldap2-dm.pw ldap2 account entry-status "uid=lockout-test,ou=people,dc=lab,dc=example,dc=com"

Sample output:

output
Entry DN: uid=lockout-test,ou=people,dc=lab,dc=example,dc=com
Entry Creation Date: 20260716064103Z (2026-07-16 06:41:03)
Entry Modification Date: 20260716064103Z (2026-07-16 06:41:03)
Entry State: activated

Request verbose details when your build supports them:

bash
dsidm -D "cn=Directory Manager" -y /root/ldap2-dm.pw ldap2 account entry-status -V "uid=lockout-test,ou=people,dc=lab,dc=example,dc=com"

On the tested 389 DS 3.2.0 build, -V returned the same fields as the default view. Use it when your documentation or automation expects the verbose flag.

Also inspect the operational attributes that drive automatic policies with ldapsearch:

bash
ldapsearch -LLL -x -H ldaps://127.0.0.1:1636 -D "cn=Directory Manager" -y /root/ldap2-dm.pw -b "uid=inactive-test,ou=people,dc=lab,dc=example,dc=com" -s base nsAccountLock lastLoginTime createTimestamp passwordExpirationTime acctPolicySubentry

Before Account Policy configuration, lastLoginTime and acctPolicySubentry may be absent. An automatically inactive account may not have nsAccountLock: true; the plug-in can reject the bind based on its time-state attributes alone.


Disable one user account manually

Lock the account with dsidm account lock:

bash
dsidm -D "cn=Directory Manager" -y /root/ldap2-dm.pw ldap2 account lock "uid=lockout-test,ou=people,dc=lab,dc=example,dc=com"

Sample output:

output
Entry uid=lockout-test,ou=people,dc=lab,dc=example,dc=com is locked

Verify the state:

bash
dsidm -D "cn=Directory Manager" -y /root/ldap2-dm.pw ldap2 account entry-status "uid=lockout-test,ou=people,dc=lab,dc=example,dc=com"

Sample output excerpt:

output
Entry State: directly locked through nsAccountLock

Test the user bind over LDAPS:

bash
ldapwhoami -x -H ldaps://127.0.0.1:1636 -D "uid=lockout-test,ou=people,dc=lab,dc=example,dc=com" -y /root/lockout-test-ldap2.pw

Sample output:

output
ldap_bind: Server is unwilling to perform (53)
	additional info: Account inactivated. Contact system administrator.

The LDAP entry remains present and searchable according to its ACIs, but the user cannot authenticate.


Disable an account with nsAccountLock

The equivalent LDIF method sets the attribute directly:

bash
cat > /tmp/disable-lockout-test.ldif <<'EOF'
dn: uid=lockout-test,ou=people,dc=lab,dc=example,dc=com
changetype: modify
replace: nsAccountLock
nsAccountLock: true
EOF

Apply the inactivation change with ldapmodify:

bash
ldapmodify -x -H ldaps://127.0.0.1:1636 -D "cn=Directory Manager" -y /root/ldap2-dm.pw -f /tmp/disable-lockout-test.ldif

A successful modify exits silently. Confirm the attribute:

bash
ldapsearch -LLL -x -H ldaps://127.0.0.1:1636 -D "cn=Directory Manager" -y /root/ldap2-dm.pw -b "uid=lockout-test,ou=people,dc=lab,dc=example,dc=com" -s base nsAccountLock

Sample output:

output
dn: uid=lockout-test,ou=people,dc=lab,dc=example,dc=com
nsAccountLock: true

dsidm account lock is preferable for routine administration because it provides a clearer account-management interface. Use direct LDAP modification when you automate through LDIF, repair account state, or work with tooling that does not provide dsidm.


Reactivate a manually disabled account

Unlock the account:

bash
dsidm -D "cn=Directory Manager" -y /root/ldap2-dm.pw ldap2 account unlock "uid=lockout-test,ou=people,dc=lab,dc=example,dc=com"

Sample output:

output
Entry uid=lockout-test,ou=people,dc=lab,dc=example,dc=com is unlocked
The entry was directly locked

Verify status:

bash
dsidm -D "cn=Directory Manager" -y /root/ldap2-dm.pw ldap2 account entry-status "uid=lockout-test,ou=people,dc=lab,dc=example,dc=com"

Sample output excerpt:

output
Entry State: activated

Test authentication over LDAPS:

bash
ldapwhoami -x -H ldaps://127.0.0.1:1636 -D "uid=lockout-test,ou=people,dc=lab,dc=example,dc=com" -y /root/lockout-test-ldap2.pw

Sample output:

output
dn: uid=lockout-test,ou=people,dc=lab,dc=example,dc=com

If you manage the attribute directly, set nsAccountLock to false or remove it, then confirm that no other policy still denies the bind.


Disable multiple users through a role

Use a managed role when several users should be disabled together. Role creation and membership belong in roles vs groups. I'll create the role and assign members before locking it:

bash
dsidm -D "cn=Directory Manager" -y /root/ldap2-dm.pw -b "dc=lab,dc=example,dc=com" ldap2 role create-managed --cn SuspendedUsers

Sample output:

output
Successfully created SuspendedUsers

I write an LDIF that adds the suspended role to user1:

bash
cat > /tmp/user1-suspended-role.ldif <<'EOF'
dn: uid=user1,ou=people,dc=lab,dc=example,dc=com
changetype: modify
add: nsRoleDN
nsRoleDN: cn=SuspendedUsers,dc=lab,dc=example,dc=com
EOF

I apply the role membership change with ldapmodify:

bash
ldapmodify -x -H ldaps://127.0.0.1:1636 -D "cn=Directory Manager" -y /root/ldap2-dm.pw -f /tmp/user1-suspended-role.ldif
WARNING
Protect nsRoleDN with ACIs before using a managed role for account inactivation. If users can remove the locked role from their own entries, an already authenticated user may remove nsRoleDN and reactivate themselves. Filtered and nested roles require equivalent protection for the attributes that determine role membership.

This lab uses:

text
cn=SuspendedUsers,dc=lab,dc=example,dc=com

Lock the role:

bash
dsidm -D "cn=Directory Manager" -y /root/ldap2-dm.pw ldap2 role lock "cn=SuspendedUsers,dc=lab,dc=example,dc=com"

Sample output:

output
Entry cn=SuspendedUsers,dc=lab,dc=example,dc=com is locked

Member binds are rejected while the role is locked:

output
ldap_bind: Server is unwilling to perform (53)
	additional info: Account inactivated. Contact system administrator.

Reactivate members by unlocking the role:

bash
dsidm -D "cn=Directory Manager" -y /root/ldap2-dm.pw ldap2 role unlock "cn=SuspendedUsers,dc=lab,dc=example,dc=com"

Locking a role causes Directory Server to compute nsAccountLock: true for its members. It does not need to write a physical nsAccountLock value to every member entry. The role definition itself remains the membership definition. Verify the exact role DN returned by dsidm role list or an explicit ldapsearch before you lock it.


Find manually disabled accounts

Search for explicit manual locks:

bash
ldapsearch -LLL -x -H ldaps://127.0.0.1:1636 -D "cn=Directory Manager" -y /root/ldap2-dm.pw -b "dc=lab,dc=example,dc=com" "(nsAccountLock=true)" dn uid nsAccountLock

This search finds nsAccountLock=true entries, including some role-related states. It does not necessarily identify users denied only by Account Policy time evaluation. When a bind still fails, check dsidm account entry-status and the operational attributes on the user entry.


Understand the Account Policy plug-in

Automatic inactivity follows this flow:

text
User attempts an LDAP bind
        |
        v
Account Policy plug-in reads the configured state attribute
        |
        v
Current time - stored timestamp is compared with accountInactivityLimit
        |
        +--> Within limit: authentication continues
        |
        └--> Limit exceeded: authentication is denied
Attribute or setting Scope Purpose
stateAttrName Instance plug-in configuration Primary timestamp checked by the plug-in
altStateAttrName Instance plug-in configuration Alternate timestamp
specAttrName Instance plug-in configuration Identifies policy entries
limitAttrName Instance plug-in configuration Names the limit attribute in policy entries
checkAllStateAttrs Instance plug-in configuration Evaluates both state attributes when enabled
alwaysRecordLogin Instance plug-in configuration Updates the login timestamp after successful authentication
acctPolicySubentry Per user or virtual through CoS Refers the account to a policy entry
accountInactivityLimit Per policy entry Maximum permitted age in seconds
lastLoginTime Per user operational attribute Records the last successful login
createTimestamp Per entry operational attribute Records when the LDAP entry was created
passwordExpirationTime Per user operational attribute Records password expiry

stateAttrName, altStateAttrName, specAttrName, and limitAttrName belong to the single Account Policy plug-in configuration entry for the Directory Server instance. Individual policy entries supply different accountInactivityLimit values, but they do not independently select different state attributes. Reconfiguring the plug-in from lastLoginTime to createTimestamp changes evaluation for every account that uses that plug-in configuration.

With alwaysRecordLogin=yes, the plug-in writes lastLoginTime directly to successfully authenticating user entries. This setting is required when acctPolicySubentry is supplied virtually through CoS or roles rather than stored physically on each user. In a high-authentication environment, these timestamp updates add directory writes and potentially replication traffic, so load-test before enabling login tracking broadly.


Track the last successful login without disabling accounts

Enable the plug-in:

bash
dsconf -D "cn=Directory Manager" -y /root/ldap2-dm.pw ldap2 plugin account-policy enable

Sample output:

output
Enabled plugin 'Account Policy Plugin'

Configure login tracking on the plug-in configuration entry:

bash
dsconf -D "cn=Directory Manager" -y /root/ldap2-dm.pw ldap2 plugin account-policy config-entry set "cn=config,cn=Account Policy Plugin,cn=plugins,cn=config" --always-record-login yes --state-attr lastLoginTime

Sample output:

output
Successfully changed the cn=config,cn=Account Policy Plugin,cn=plugins,cn=config

Restart the instance so the plug-in reads the updated configuration:

bash
dsctl ldap2 restart

Authenticate as a test user over LDAPS, then read lastLoginTime:

bash
ldapwhoami -x -H ldaps://127.0.0.1:1636 -D "uid=inactive-test,ou=people,dc=lab,dc=example,dc=com" -w 'InactiveTest!2026'

I read lastLoginTime on the same account to confirm the plug-in recorded the successful bind:

bash
ldapsearch -LLL -x -H ldaps://127.0.0.1:1636 -D "cn=Directory Manager" -y /root/ldap2-dm.pw -b "uid=inactive-test,ou=people,dc=lab,dc=example,dc=com" -s base lastLoginTime

Sample output:

output
dn: uid=inactive-test,ou=people,dc=lab,dc=example,dc=com
lastLoginTime: 20260716070904Z

Directory Server can use the Account Policy plug-in solely to maintain lastLoginTime; no expiration or inactivity policy is required for this tracking-only mode.


Automatically inactivate accounts after inactivity

Configure the plug-in for inactivity evaluation. The important --alt-state-attr 1.1 setting disables the createTimestamp fallback so existing users without lastLoginTime are not treated as already inactive:

bash
dsconf -D "cn=Directory Manager" -y /root/ldap2-dm.pw ldap2 plugin account-policy config-entry set "cn=config,cn=Account Policy Plugin,cn=plugins,cn=config" --always-record-login yes --state-attr lastLoginTime --alt-state-attr 1.1 --spec-attr acctPolicySubentry --limit-attr accountInactivityLimit

Restart the instance after changing plug-in configuration:

bash
dsctl ldap2 restart

Create the account inactivity policy

bash
cat > /tmp/account-inactivity-policy.ldif <<'EOF'
dn: cn=Account Inactivity Policy,dc=lab,dc=example,dc=com
objectClass: top
objectClass: ldapsubentry
objectClass: extensibleObject
objectClass: accountpolicy
cn: Account Inactivity Policy
accountInactivityLimit: 1814400
EOF

I add the policy entry to the directory with ldapadd:

bash
ldapadd -x -H ldaps://127.0.0.1:1636 -D "cn=Directory Manager" -y /root/ldap2-dm.pw -f /tmp/account-inactivity-policy.ldif

1814400 seconds equals 21 days.

Apply the policy with CoS

The continuous lab assigns the inactivity policy through CoS to users below ou=people,dc=lab,dc=example,dc=com. Skip the alternative direct-assignment section below. The test intentionally ages only inactive-test; other users in the same scope remain active while their timestamps are within the limit. Do not also add a direct acctPolicySubentry value to the same user.

Create the template and pointer definition:

bash
cat > /tmp/account-policy-cos.ldif <<'EOF'
dn: cn=AccountInactivityTemplate,dc=lab,dc=example,dc=com
objectClass: top
objectClass: ldapsubentry
objectClass: extensibleObject
objectClass: cosTemplate
cn: AccountInactivityTemplate
acctPolicySubentry: cn=Account Inactivity Policy,dc=lab,dc=example,dc=com

dn: cn=AccountInactivityDefinition,ou=people,dc=lab,dc=example,dc=com
objectClass: top
objectClass: ldapsubentry
objectClass: cosSuperDefinition
objectClass: cosPointerDefinition
cn: AccountInactivityDefinition
cosTemplateDn: cn=AccountInactivityTemplate,dc=lab,dc=example,dc=com
cosAttribute: acctPolicySubentry default operational-default
EOF

I add the CoS template and pointer definition so users under ou=people inherit the policy:

bash
ldapadd -x -H ldaps://127.0.0.1:1636 -D "cn=Directory Manager" -y /root/ldap2-dm.pw -f /tmp/account-policy-cos.ldif

Verify the virtual association on the test user:

bash
ldapsearch -LLL -x -H ldaps://127.0.0.1:1636 -D "cn=Directory Manager" -y /root/ldap2-dm.pw -b "uid=inactive-test,ou=people,dc=lab,dc=example,dc=com" -s base acctPolicySubentry

Sample output:

output
dn: uid=inactive-test,ou=people,dc=lab,dc=example,dc=com
acctPolicySubentry: cn=Account Inactivity Policy,dc=lab,dc=example,dc=com

CoS allows one policy to apply across a subtree without adding a physical acctPolicySubentry value to every user. Keep the detailed CoS architecture in Class of Service.


Alternative: Assign the policy directly instead of using CoS

Direct assignment is an alternative to CoS. Skip the entire CoS template and definition procedure when using this method. The example below assigns the policy physically to inactive-test.

bash
cat > /tmp/assign-inactivity-policy.ldif <<'EOF'
dn: uid=inactive-test,ou=people,dc=lab,dc=example,dc=com
changetype: modify
add: acctPolicySubentry
acctPolicySubentry: cn=Account Inactivity Policy,dc=lab,dc=example,dc=com
EOF

I apply the direct policy assignment with ldapmodify:

bash
ldapmodify -x \
  -H ldaps://127.0.0.1:1636 \
  -D "cn=Directory Manager" \
  -y /root/ldap2-dm.pw \
  -f /tmp/assign-inactivity-policy.ldif

Use direct acctPolicySubentry references for small exceptions or different limits per user. Use CoS when the same policy applies to many users in a subtree. Do not run both procedures in the same lab run.


Test automatic account inactivity

Set an old lastLoginTime on the test account so the policy limit is already exceeded:

bash
cat > /tmp/old-last-login.ldif <<'EOF'
dn: uid=inactive-test,ou=people,dc=lab,dc=example,dc=com
changetype: modify
replace: lastLoginTime
lastLoginTime: 20260101000000Z
EOF

I apply the aged timestamp so the inactivity policy should deny the next bind:

bash
ldapmodify -x -H ldaps://127.0.0.1:1636 -D "cn=Directory Manager" -y /root/ldap2-dm.pw -f /tmp/old-last-login.ldif

Attempt a bind over LDAPS:

bash
ldapwhoami -x -H ldaps://127.0.0.1:1636 -D "uid=inactive-test,ou=people,dc=lab,dc=example,dc=com" -w 'InactiveTest!2026'

Sample output:

output
ldap_bind: Constraint violation (19)
	additional info: Account inactivity limit exceeded. Contact system administrator to reset.

Confirm that another recently active user within the same CoS scope can still authenticate. This proves that policy assignment alone does not block an account whose timestamp is within the configured limit.


Reactivate an automatically inactivated account

Try dsidm account unlock first:

bash
dsidm -D "cn=Directory Manager" -y /root/ldap2-dm.pw ldap2 account unlock "uid=inactive-test,ou=people,dc=lab,dc=example,dc=com"

On the tested 389 DS 3.2.0 build, that command returned Error: float() argument must be a string or a real number, not 'NoneType' and did not restore access while lastLoginTime still exceeded the policy limit. For lastLoginTime inactivity, set the timestamp to the current time:

I capture the current UTC timestamp for the recovery LDIF:

bash
CURRENT_LOGIN_TIME=$(date -u +%Y%m%d%H%M%SZ)

I write an LDIF that replaces lastLoginTime with that value:

bash
cat > /tmp/reactivate-last-login.ldif <<EOF
dn: uid=inactive-test,ou=people,dc=lab,dc=example,dc=com
changetype: modify
replace: lastLoginTime
lastLoginTime: ${CURRENT_LOGIN_TIME}
EOF

I apply the timestamp reset with ldapmodify:

bash
ldapmodify -x -H ldaps://127.0.0.1:1636 -D "cn=Directory Manager" -y /root/ldap2-dm.pw -f /tmp/reactivate-last-login.ldif

Verify a successful bind:

bash
ldapwhoami -x -H ldaps://127.0.0.1:1636 -D "uid=inactive-test,ou=people,dc=lab,dc=example,dc=com" -w 'InactiveTest!2026'

Sample output:

output
dn: uid=inactive-test,ou=people,dc=lab,dc=example,dc=com

Recovery depends on which state denied the bind:

Policy state Correct recovery
lastLoginTime Set it to the current time or use a working dsidm account unlock
createTimestamp Do not rewrite the creation timestamp; remove or change the policy assignment, increase the limit, or recreate the disposable account
passwordExpirationTime Reset the password or correct the password-expiration policy so Directory Server recalculates password state
Manual nsAccountLock Run dsidm account unlock or remove or set nsAccountLock to false
Locked role Unlock the role

Simply clearing nsAccountLock does not correct an expired lastLoginTime.


Automatically inactivate accounts based on creation time

Account-age expiration uses createTimestamp as the state attribute:

text
state attribute: createTimestamp
limit attribute: accountInactivityLimit

Typical use case:

text
Disable contractor or temporary accounts 60 days after creation.

The Account Policy plug-in has one active state-attribute configuration per Directory Server instance. Changing stateAttrName from lastLoginTime to createTimestamp changes how all assigned Account Policy entries on that instance are evaluated. Do not present creation-age expiration as an additional independent policy running beside the existing inactivity configuration. Use a separate test instance, or deliberately replace the current plug-in configuration after assessing every assigned account.


Automatically inactivate accounts after password expiration

Password-expiry inactivation evaluates passwordExpirationTime through the Account Policy plug-in. This replaces the lastLoginTime-based evaluation configuration rather than applying only to one policy entry:

bash
dsconf -D "cn=Directory Manager" -y /root/ldap2-dm.pw ldap2 plugin account-policy config-entry set "cn=config,cn=Account Policy Plugin,cn=plugins,cn=config" --always-record-login yes --always-record-login-attr lastLoginTime --state-attr non_existent_attribute --alt-state-attr passwordExpirationTime --spec-attr acctPolicySubentry --limit-attr accountInactivityLimit

Restart the instance after changing plug-in configuration:

bash
dsctl ldap2 restart
Behaviour Ordinary password-policy expiration Account Policy password-expiry mode
Expired password May allow bind and password change within grace rules Can fully deny authentication after the configured interval
Recovery User self-service or administrator password reset Reset the password or correct password-expiration policy so Directory Server recalculates passwordExpirationTime

Configure password expiration in global, subtree, and user password policy. Use Account Policy enforcement when you need to block authentication completely after the post-expiration interval.


Combine login inactivity and password-expiration checks

checkAllStateAttrs evaluates both lastLoginTime and passwordExpirationTime when enabled, but the alternate attribute must actually be passwordExpirationTime. Do not leave the --alt-state-attr 1.1 inactivity configuration in place and assume that enabling checkAllStateAttrs alone adds password-expiration checks.

bash
dsconf -D "cn=Directory Manager" -y /root/ldap2-dm.pw ldap2 plugin account-policy config-entry set "cn=config,cn=Account Policy Plugin,cn=plugins,cn=config" --always-record-login yes --state-attr lastLoginTime --alt-state-attr passwordExpirationTime --spec-attr acctPolicySubentry --limit-attr accountInactivityLimit --check-all-state-attrs yes

Restart the instance so the plug-in loads the updated configuration:

bash
dsctl ldap2 restart
IMPORTANT
Test the exact plug-in configuration against your installed 389 Directory Server version. Do not combine checkAllStateAttrs with createTimestamp; Red Hat explicitly warns that this can unexpectedly lock accounts.

Audit disabled and inactive accounts

Use separate checks for different mechanisms.

Manually disabled accounts:

bash
ldapsearch -LLL -x -H ldaps://127.0.0.1:1636 -D "cn=Directory Manager" -y /root/ldap2-dm.pw -b "dc=lab,dc=example,dc=com" "(nsAccountLock=true)" dn uid

Accounts with a recorded login time:

bash
ldapsearch -LLL -x -H ldaps://127.0.0.1:1636 -D "cn=Directory Manager" -y /root/ldap2-dm.pw -b "dc=lab,dc=example,dc=com" "(lastLoginTime=*)" dn uid lastLoginTime

Accounts assigned an inactivity policy:

bash
ldapsearch -LLL -x -H ldaps://127.0.0.1:1636 -D "cn=Directory Manager" -y /root/ldap2-dm.pw -b "dc=lab,dc=example,dc=com" "(acctPolicySubentry=*)" dn uid acctPolicySubentry lastLoginTime

For CoS-generated operational values, request operational attributes explicitly where required. Determining whether an account is currently past its inactivity limit may require comparing the timestamp with the policy's accountInactivityLimit.


Troubleshoot account disabling and inactivity

Symptom Likely cause Fix
dsidm account lock succeeds but the user can still bind Wrong user DN, wrong server, replication lag, or authentication passed to another provider Confirm the DN, target instance, nsAccountLock, and bind destination
Account is active but authentication still fails Failed-password lockout, password expiration, Account Policy plug-in, invalid password, disabled role, or pass-through authentication Inspect operational attributes and the effective policy for each mechanism
Existing users are disabled immediately after enabling inactivity Users lacked lastLoginTime and the plug-in fell back to an old createTimestamp Use --alt-state-attr 1.1 until users record lastLoginTime, or introduce tracking before enforcement
lastLoginTime is not updated Plug-in disabled, alwaysRecordLogin off, wrong configuration entry, or missing restart Enable the plug-in, verify config-entry show, restart the instance, and bind again
Policy applies to the wrong users Incorrect acctPolicySubentry, CoS template, or CoS definition Verify the policy DN, subtree placement, and operational CoS values
account unlock does not restore access Account is past its inactivity limit rather than manually locked Use the recovery table for the evaluated state attribute
User becomes inactive again immediately lastLoginTime, createTimestamp, or passwordExpirationTime still exceeds the policy limit Set a current timestamp, adjust the policy, or change the plug-in configuration before retesting
User removes a locked role and regains access nsRoleDN or role-determining attributes are writable by the user Restrict self-modification of nsRoleDN and filtered-role attributes with ACIs

Security and operational recommendations

  • Disable accounts instead of deleting them when access may need to be restored.
  • Keep manual disabling and automatic inactivity clearly distinguishable in runbooks.
  • Use dsidm for routine lock, unlock, and status operations when your instance supports it.
  • Protect nsRoleDN and role-determining attributes before using roles for inactivation.
  • Use CoS for one policy applied to a large subtree; use direct acctPolicySubentry values for small exceptions.
  • Introduce lastLoginTime tracking before enforcing an inactivity limit.
  • Avoid using an old createTimestamp as an accidental fallback.
  • Test with a small pilot subtree before applying inactivity globally.
  • Audit service accounts before applying interactive-user inactivity rules.
  • Use short inactivity limits only in test environments.
  • Document which timestamp caused every automatic account denial.
  • Bind over LDAPS or STARTTLS for password authentication in production.

What's Next


References


Summary

  1. Use dsidm account lock to disable a user manually and dsidm account unlock to restore manual locks.
  2. Use roles when several users should be disabled together, and protect nsRoleDN with ACIs.
  3. Remember that Account Policy state attributes are configured once per instance.
  4. Enable the Account Policy plug-in to record lastLoginTime, then apply accountInactivityLimit through CoS or direct assignment.
  5. Test automatic inactivity with a dedicated account and an old lastLoginTime.
  6. Reactivate accounts using the recovery path that matches the evaluated state attribute.
  7. Keep failed-password lockout in account lockout.

Frequently Asked Questions

1. What is the difference between nsAccountLock and Account Policy inactivity in 389 Directory Server?

A direct user lock sets or manages nsAccountLock on the user entry through dsidm account lock or ldapmodify. Locking a role instead causes Directory Server to compute nsAccountLock: true for role members without writing that value physically to every member. Account Policy inactivity evaluates timestamps and can deny authentication without either type of explicit user lock.

2. Does dsidm account unlock clear Account Policy inactivity on every release?

Recovery depends on which state denied the bind. Manual nsAccountLock and locked roles use dsidm account unlock or role unlock. lastLoginTime inactivity may respond to dsidm account unlock on some builds, but the tested 3.2.0 build required updating lastLoginTime to the current time. createTimestamp and passwordExpirationTime modes need different recovery steps.

3. How do I find manually disabled users in 389 Directory Server?

Search for (nsAccountLock=true) under your suffix. That finds explicit manual locks and some role-related states, but it does not identify every account denied only by Account Policy time evaluation.

4. Should I use createTimestamp as the alternate Account Policy state attribute?

Only with a validated design on a separate test instance. stateAttrName and altStateAttrName belong to the single plug-in configuration entry for the instance. Changing them affects every account evaluated by that plug-in. The tested inactivity lab uses --alt-state-attr 1.1 so users without lastLoginTime are not judged from an old createTimestamp.

5. Where does failed-password lockout fit?

Failed-password lockout uses password-policy counters such as passwordRetryCount and accountUnlockTime. It is configured and recovered in the dedicated account lockout chapter, not in this account-lifecycle guide.
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 …