Accounts with "Password never expires" set are where Active Directory keeps its oldest passwords, and the oldest passwords are the ones most likely to be weak, shared, reused or already in someone else's hands. The flag itself is not always wrong: current NIST guidance says verifiers should not force periodic changes, and Microsoft recommends no expiry for cloud-only accounts. What makes an account risky is the combination: never-expiring plus a password set years ago by a person, plus an SPN, privileged group membership or no owner. This runbook shows how to find every account with the flag, enrich it with the attributes that matter, sort the deliberate from the dangerous, and fix the dangerous ones without breaking the services that depend on them. It is for Active Directory administrators and identity security teams. Retiring accounts that carry SPNs is covered in How to find and retire Active Directory service accounts with SPNs.
This is an independent guide from NHI Mgmt Group, based on Microsoft's public documentation and NIST SP 800-63B as of November 2026. It is not sponsored or reviewed by Microsoft. Product features change, so confirm settings against Microsoft's current documentation.
Key takeaways
- It is one bit. The flag is
DONT_EXPIRE_PASSWORDinuserAccountControl, value 0x10000 (decimal 65536). LDAP bitwise filters need the decimal value. - The flag is a lead, not a verdict. NIST SP 800-63B (section 3.1.1.2) says verifiers shall not require periodic password changes, but shall force a change on evidence of compromise. A long, unique, MFA-protected human password with no expiry can be correct policy.
- Password age is the real risk signal. Pair the flag with
pwdLastSet: a never-expiring password set five years ago on an account nobody owns is the finding. - Service accounts are most of the list. Expect the flag on accounts that run services, scheduled tasks and integrations, because an expiry would break them. The fix there is a managed account or a vaulted, rotated password, not just clearing the flag.
- Clearing the flag can lock things out. If the password is older than the domain maximum age, removing the flag makes it expire at once. Reset first, then change the flag, in a change window.
When you need this
- Audit or pentest finding: "accounts with non-expiring passwords" is a staple of AD assessments and of control testing against NIST SP 800-53 IA-5(1) Password-based Authentication.
- Service account programme start: the never-expiring list is the fastest way to seed a service account inventory. See the Service Account Inventory Template.
- After a credential leak or password spraying campaign (ATT&CK T1110.003): old, never-changed passwords are the ones most likely to be in breach corpora or guessable.
- Before tightening password policy: changing the domain or fine-grained policy does nothing for flagged accounts, so you need the list before the policy change.
Before you start
- Tools: the ActiveDirectory PowerShell module on an admin workstation, read access to every domain in the forest, and your SIEM or domain controller Security logs.
- Audit policy: confirm Audit User Account Management is enabled on domain controllers so event 4738 ("A user account was changed") is generated. It records changes to the flag.
- Policy context: record the domain's maximum password age and any fine-grained password policies. An account without the flag can still effectively never expire if its policy has a maximum age of 0, so check both.
- Agree the house rule: decide in writing which account types may hold the flag (for example: break-glass accounts with long random vaulted passwords, human accounts covered by MFA and breached-password checks) and which may not. Triage is quick once the rule exists.
Where never-expiring passwords come from
| Source | How to see it | Typical owner |
|---|---|---|
DONT_EXPIRE_PASSWORD flag (0x10000) on the account | LDAP filter on userAccountControl, or Search-ADAccount -PasswordNeverExpires | Whoever created the account; often set at build time for services |
| Domain password policy with maximum age 0 | Default Domain Policy settings | Domain administrators; affects every account without a fine-grained policy |
| Fine-grained password policy (PSO) with no or very long maximum age | Password Settings Container; resultant policy per user | Often created for service account groups |
| Cloud-only Entra ID user with per-user never-expire set | Microsoft Graph user passwordPolicies | Entra administrators (User Administrator or above) |
Step-by-step
Steps 1 to 5 are read-only. Steps 6 to 8 change the directory and are reversible if you keep the baseline export.
- List every account with the flag. Run
Search-ADAccount -PasswordNeverExpires -UsersOnly, adding-SearchBaseto scope by OU. For the attributes you need for triage, use an LDAP bitwise AND filter (matching rule1.2.840.113556.1.4.803) withGet-ADUser -LDAPFilter '(userAccountControl:1.2.840.113556.1.4.803:=65536)' -Properties pwdLastSet,PasswordLastSet,LastLogonDate,Enabled,servicePrincipalName,memberOf,adminCount,Description,whenCreated,msDS-SupportedEncryptionTypesand export to CSV. Run it against each domain in the forest. Without-UsersOnly,Search-ADAccountalso returns computer and managed service accounts, which you can set aside: their passwords are machine-generated and rotated. - Turn pwdLastSet into an age.
pwdLastSetis a count of 100-nanosecond intervals since 1 January 1601 (UTC); the module'sPasswordLastSetproperty gives it as a date. A value of 0 normally means "must change at next logon", except when the never-expire flag is set, so treat 0 on a flagged account as a password state to investigate, not a date. Bucket ages: under 1 year, 1 to 3 years, 3 to 5 years, over 5 years. - Add the risk attributes. For each account mark: enabled or disabled; holds an SPN (Kerberoastable, ATT&CK T1558.003); member of a privileged group directly or nested, or
adminCount= 1;PASSWD_NOTREQD(0x0020) also set;DONT_REQ_PREAUTH(0x400000) also set; and last logon.LastLogonDateis replicated with a lag and approximate, so treat it as a hint and confirm with logon events before acting. - Classify the account type. Use naming, OU, description and where it logs on to sort into: service or integration account, human user, break-glass or built-in Administrator, shared or generic account, test account. Event 4624 logon types on member servers show service (type 5) and batch (type 4) logons, which identify where a credential is configured as a service or scheduled task identity.
- Find an owner. Every account needs a named person or team. No owner after a reasonable search means the account is a disable candidate, not a keep.
- Fix by category using the triage table below. For service accounts, the target is a group managed service account (gMSA) or, where that is impossible, a long random password held in a vault and rotated by it. See How to convert AD service accounts to gMSA.
- Reset before you clear the flag. For accounts that should expire, set a new password first, update every place it is configured, then clear the flag with
Set-ADUser <account> -PasswordNeverExpires $false. Microsoft documents that this parameter sets theADS_UF_DONT_EXPIRE_PASSWDflag, and that it cannot be set to true on an account that also hasChangePasswordAtLogonset to true. - Disable what nobody owns.
Set-ADUser <account> -Enabled $falsewith a description holding the date and change reference, move it to a quarantine OU, and leave it through a full business cycle before deletion. Disabling does not touch the password, so re-enabling restores service.
Triage table
| What you find | Risk | Action |
|---|---|---|
| Enabled, privileged group member, password over 1 year old | Highest: one guessed or cracked password gives domain-level access | Remove privilege or reset to a vaulted random password now; review logon history for misuse |
| Enabled, SPN, password over 3 years old | Kerberoastable with a likely human-chosen password | Convert to gMSA; otherwise long random password, AES only |
| Enabled, no logons in 90 days, no owner | Dormant credential nobody will miss or notice | Disable, quarantine, delete after the quiet period |
| Human user, MFA enforced, breached-password checks in place | Low, and consistent with NIST SP 800-63B | Keep; document as accepted under the house rule |
| Human user, no MFA, password over 1 year old | Spraying and reuse target | Enforce MFA, reset, consider clearing the flag until MFA is in place |
| Break-glass or built-in Administrator | Expected to be non-expiring; risk is in storage and monitoring | Keep the flag, confirm the password is long, random and vaulted, alert on every sign-in. See the Break-Glass Account Guide |
Flag plus PASSWD_NOTREQD | Account may hold a blank or trivial password | Set a strong password, clear PASSWD_NOTREQD, investigate why it was set |
| Disabled, flag set | Low now; returns if someone re-enables it | Delete if confirmed unused; otherwise reset the password before any re-enable |
Small versus large estates
A single domain with a few hundred flagged accounts can be triaged in a spreadsheet with owners on a call over a couple of weeks. A multi-domain forest with thousands needs the work split by the triage table: privileged accounts first, then SPN accounts with old passwords, then dormant accounts, then everything else, with each wave given a deadline. In large estates, feed the CSV export into your identity governance or CMDB tooling so ownership is tracked, and make the export a scheduled job rather than a one-off, because new flagged accounts will keep appearing until the house rule is enforced at creation.
Verify it worked
- Re-run step 1: the count of enabled, flagged accounts with passwords older than your threshold falls, and every remaining flagged account has an owner and a documented reason.
- Watch event 4738: Microsoft's monitoring guidance for this event suggests watching "'Don't Expire Password' - Enabled" for critical accounts, or for all accounts if your organisation does not allow the flag. After the clean-up, any new instance should map to an approved request.
- Check dependent services: watch logon failures (event 4625) and service start failures for accounts you reset or disabled during the quiet period.
- Check the Password Last Set field: event 4738 also records
pwdLastSetchanges, giving evidence that resets actually happened.
Detection and metrics
| Signal or metric | Source | Target or alert condition |
|---|---|---|
| Flag enabled on any account | Event 4738, User Account Control field | Alert for privileged accounts; ticket for all others if the house rule restricts the flag |
| Enabled flagged accounts with password over 1 year old | Scheduled LDAP export | Downward trend; zero for privileged accounts |
| Flagged accounts without a named owner | Inventory | Zero |
| Flagged accounts in privileged groups | Export joined to group membership | Only break-glass accounts, each with sign-in alerting |
| Service accounts converted to gMSA or vaulted rotation | Inventory | Rising percentage per quarter |
| Logons by flagged accounts from unexpected hosts | Events 4624 and 4769 | Alert: an old service credential used from a workstation is a strong compromise signal |
Rollback and common errors
- Rollback of a flag change:
Set-ADUser <account> -PasswordNeverExpires $truerestores the previous state. Keep the baseline CSV from step 1 so you know which accounts had it. - Rollback of a disable:
Set-ADUser <account> -Enabled $trueand move it back to its original OU. - Rollback of a reset: there is none. A reset password cannot be restored, so update every dependent service, task and connection string in the same change window and test before closing the change.
- Error: clearing the flag on an old password. If
pwdLastSetis older than the maximum password age, the account's password is treated as expired the moment the flag goes, and services stop. Reset first. - Error: treating every flagged human account as a violation. Forcing expiry back on well-protected human accounts contradicts NIST SP 800-63B and Microsoft's own recommendation for cloud accounts, and tends to produce weaker passwords. Spend the effort on service and privileged accounts.
- Error: looking only at the flag. Accounts under a fine-grained policy or a domain policy with maximum age 0 never expire either, and will not appear in a
userAccountControlquery. - Error: forgetting synced identities. On-premises accounts synchronised to Entra ID are governed by the on-premises policy for expiry unless the tenant enables
CloudPasswordPolicyForPasswordSyncedUsersEnabled. Cloud-only accounts carry their own per-user setting, so review that list in Entra separately. - Error: missing the hosts. A service account password configured on a server nobody remembers will break silently after a reset. Use 4624 service and batch logons to map every host before you change anything.
Hardening afterwards
- Enforce the rule at creation: provisioning workflows should refuse the flag except for the approved account types, and alert on 4738 when it is set outside them.
- Move services to managed identities: gMSA passwords are generated and rotated by Active Directory. See the Group Managed Service Accounts (gMSA) Guide.
- Vault what cannot be converted: put remaining service passwords under a PAM or secrets tool that rotates them on a schedule and on evidence of compromise, which is the trigger NIST requires.
- Check human passwords against breach lists: where human accounts keep non-expiring passwords, pair them with MFA and breached-password screening. See Breached password checks.
- Review on a schedule: include flagged accounts in the quarterly service account access review. See How to Run an Access Review for Service Accounts.
Framework mapping
| Framework | Reference | How this runbook supports it |
|---|---|---|
| NIST SP 800-63B | Section 3.1.1.2 Password Verifiers | No forced periodic change, forced change on evidence of compromise |
| NIST SP 800-53 Rev 5 | IA-5(1) Password-based Authentication; IA-5 Authenticator Management | Password age, strength and change triggers managed per account type |
| NIST SP 800-53 Rev 5 | AC-2 Account Management; AC-2(3) Disable Accounts | Owners assigned; dormant flagged accounts disabled |
| NIST CSF 2.0 | PR.AA-01 | Credentials for users and services managed by the organisation |
| MITRE ATT&CK v20 | T1078.002 Domain Accounts; T1110.003 Password Spraying | Fewer old, guessable domain passwords available to reuse or spray |
| MITRE ATT&CK v20 | T1558.003 Kerberoasting; T1201 Password Policy Discovery | Old SPN passwords removed; less to gain from learning the policy |
Practitioner checklist
- Write the house rule for which account types may hold a non-expiring password.
- Export every user account with
userAccountControlbit 65536 set, in every domain, withpwdLastSet, SPNs, group membership and last logon. - Check domain and fine-grained policies for maximum age 0 or very long ages.
- Bucket by password age and flag privileged, SPN,
PASSWD_NOTREQDand pre-authentication-disabled accounts. - Assign an owner to every flagged account, or disable it.
- Reset privileged and old SPN passwords first, then convert to gMSA or vaulted rotation.
- Reset before clearing the flag, and map dependent hosts from 4624 service and batch logons.
- Alert on event 4738 "Don't Expire Password" enabled outside approved accounts.
- Review cloud-only Entra accounts with per-user never-expire separately.
- Schedule the export monthly and track the metrics above.
Standards and references
- NIST: SP 800-63B Authentication and Authenticator Management
- Microsoft Learn: Use the UserAccountControl flags to manipulate user account properties
- Microsoft Learn: Search-ADAccount
- Microsoft Learn: Set-ADUser
- Microsoft Learn: Search Filter Syntax
- Microsoft Learn: Pwd-Last-Set attribute
- Microsoft Learn: 4738(S) A user account was changed
- Microsoft Learn: Password policy recommendations for Microsoft 365
Related NHI Mgmt Group resources: How to find and retire Active Directory service accounts with SPNs, Service Account Security Guide, Password Policy Guide (NIST-aligned), Kerberoasting and AS-REP Roasting Defence Guide.