Event 4625 — An Account Failed to Log On
Event 4625 fires on every failed logon anywhere in the domain, and the event’s own Failure Reason text is generic — the numeric Logon Type, Status, and Sub Status fields are what actually separate a mistyped password from a locked-out account or a credential-stuffing attempt.
A numeric code describing how the logon was attempted — console, network, RDP, service, and more.
Hexadecimal NTSTATUS codes that carry the actual failure reason, decoded against the MS-ERREF reference.
A short, generic human-readable string tied to the Status field — never specific enough on its own.
Introduction
A user’s account keeps locking out and nobody can say why. Someone opens the Security log on the domain controller that issued the lockout and finds a wall of Event ID 4625 entries — same account, same rough time window, and a Failure Reason that just reads “Unknown user name or bad password” on every single one. That text alone does not say whether the account is being hit by a phone with a stale saved Wi-Fi password, a scheduled task still holding last month’s credential, or someone at a keyboard three time zones away actually guessing.
The two fields that answer it — Logon Type and the hexadecimal Status / Sub Status pair — sit further down the event than most people scroll before deciding it is not worth reading closely.
What You’re Seeing
A typical 4625 on a member server, from a failed Remote Desktop logon with a wrong password, renders with these fields:
# Security log on the target server, Event ID 4625, Task Category: Logon
An account failed to log on.
Subject:
Security ID: NT AUTHORITY\SYSTEM
Account Name: FIN-APP01$
Account Domain: CORP
Logon Type: 10
Account For Which Logon Failed:
Security ID: S-1-0-0
Account Name: jdoe
Account Domain: CORP
Failure Information:
Failure Reason: Unknown user name or bad password.
Status: 0xC000006D
Sub Status: 0xC000006A
Network Information:
Workstation Name: FIN-LAPTOP03
Source Network Address: 10.20.4.118
Source Port: 51422
Detailed Authentication Information:
Logon Process: NtLmSsp
Authentication Package: NTLM
Package Name (NTLM only): NTLM V2
A locked-out account looks similar but carries a different Status pair and, notably, no Sub Status detail:
# Same server, same event ID, account already locked out
Logon Type: 3
Failure Information:
Failure Reason: Account locked out.
Status: 0xC0000234
Sub Status: 0x0
auditpol /get /category:"Logon/Logoff" before assuming nothing is failing.
What It Actually Means
Think of Status and Sub Status as an outer envelope and the letter inside it. For the credential-family failures — wrong password, account does not exist — Windows almost always writes the same generic outer code, 0xC000006D (STATUS_LOGON_FAILURE equivalent, described as “the attempted logon is invalid, either due to a bad username or authentication information”), into Status. The Sub Status field is where the specific reason actually lives: 0xC0000064 means the account does not exist at all, while 0xC000006A means the username is valid but the password supplied is wrong.
Other failure categories skip that indirection entirely and write the specific code straight into Status, leaving Sub Status at 0x0 — a locked-out account reports Status = 0xC0000234 directly, a disabled account reports 0xC0000072, and an expired account reports 0xC0000193. Knowing which pattern applies is the difference between reading the right field and staring at a generic code that tells you nothing.
Logon Type answers a different question entirely: not why the logon failed, but how it was being attempted. A failed Type 2 means someone typed a wrong password at a physical keyboard; a failed Type 10 means the same wrong password over Remote Desktop; a failed Type 3 against a service account from an unfamiliar IP address is a very different story from a failed Type 2 on the account owner’s usual workstation.
Reading the Logon Type Codes
The Logon Type field uses a fixed set of numeric codes, documented alongside Event 4624 (the successful counterpart to 4625) and shared by both events:
| Type | Name | Meaning |
|---|---|---|
2 | Interactive | A user logged on at the local keyboard and console. |
3 | Network | A user or computer logged on from the network — SMB share access, most service-to-service calls. |
4 | Batch | Used by scheduled tasks and batch servers running on behalf of a user without direct interaction. |
5 | Service | The Service Control Manager started a service under this account. |
7 | Unlock | A locked workstation was unlocked. |
8 | NetworkCleartext | A network logon where the password crossed in unhashed form — typically Basic authentication over IIS. |
9 | NewCredentials | A process cloned its token and supplied different credentials for outbound network connections, e.g. runas /netonly. |
10 | RemoteInteractive | Terminal Services or Remote Desktop. |
11 | CachedInteractive | Logon using credentials cached locally; the domain controller was not contacted. |
How to Diagnose
Work from the log outward, and pull the account, network, and status detail together before deciding what kind of problem this is.
# 1. Pull recent 4625 events and see what's actually in the log
Get-WinEvent -FilterHashtable @{ LogName = 'Security'; Id = 4625 } -MaxEvents 200 |
Select-Object TimeCreated, Id, Message
# 2. Break events down by Status/Sub Status and Logon Type to separate noise from real problems
Get-WinEvent -FilterHashtable @{ LogName = 'Security'; Id = 4625 } -MaxEvents 500 |
ForEach-Object {
$xml = [xml]$_.ToXml()
$data = $xml.Event.EventData.Data
[pscustomobject]@{
Time = $_.TimeCreated
Account = ($data | Where-Object Name -eq 'TargetUserName').'#text'
Source = ($data | Where-Object Name -eq 'IpAddress').'#text'
LogonType = ($data | Where-Object Name -eq 'LogonType').'#text'
Status = ($data | Where-Object Name -eq 'Status').'#text'
SubStatus = ($data | Where-Object Name -eq 'SubStatus').'#text'
}
} | Group-Object Status, SubStatus | Sort-Object Count -Descending
# 3. Check whether the target account is disabled, locked out, or has an expired password
Get-ADUser -Identity jdoe -Properties Enabled, LockedOut, PasswordExpired, PasswordLastSet, BadLogonCount |
Select-Object Name, Enabled, LockedOut, PasswordExpired, PasswordLastSet, BadLogonCount
# 4. List every currently locked-out account in one pass
Search-ADAccount -LockedOut | Select-Object Name, SamAccountName, LockedOut
# 5. Confirm the audit subcategories that generate 4625 are actually enabled
auditpol /get /category:"Logon/Logoff"
Common Causes
| Cause | How to Confirm | Fix |
|---|---|---|
Wrong password (Status 0xC000006D, Sub Status 0xC000006A) |
Often a mapped drive, scheduled task, phone, or browser still holding an old password. | Find and update the stale credential source rather than resetting the account’s password again: check Get-ADUser -Identity <name> -Properties BadLogonCount, LastBadPasswordAttempt. |
Account does not exist (Status 0xC000006D, Sub Status 0xC0000064) |
Get-ADUser -Identity <name> returns nothing for the account named in the event. |
Usually a typo or a decommissioned account still referenced somewhere; if the source address is unexpected, treat repeated attempts as reconnaissance. |
Account locked out (Status 0xC0000234) |
Search-ADAccount -LockedOut lists the account generating the events. |
Confirm the lockout was triggered by a real bad-password pattern before unlocking with Unlock-ADAccount -Identity <name> — see the account lockouts post for the full policy picture. |
Account disabled (Status 0xC0000072) |
Get-ADUser -Identity <name> -Properties Enabled returns False. |
Confirm the disable was intentional; re-enable only if it was accidental, otherwise treat repeated attempts as noise from a device that has not been decommissioned. |
Account or password expired (Status 0xC0000193 or 0xC0000071) |
Get-ADUser -Identity <name> -Properties AccountExpirationDate, PasswordExpired shows the expiry. |
Extend the account expiration date if the account should still be active, or have the user change an expired password through a normal logon. |
No domain controller available to validate the logon (Status 0xC000005E) |
Failures cluster around a single site or subnet and coincide with DC or network outages. | This is a DC availability problem, not a credential problem — see the no logon servers available post for the diagnostic path. |
| Genuine password-spray or brute-force attempt | Many distinct accounts, one Status/Sub Status pair, one source address, tight timing — or one high-value account hit from an address it has never used before. | Block the source at the firewall or conditional access layer, force a reset on any account that shows a corresponding successful 4624 afterwards, and confirm the lockout threshold actually fired where it should have. |
Working Through a Fix
Group the flood by Status and Sub Status before touching a single account — the script in step 2 of the diagnosis section does this in one pass. In most environments the bulk of volume turns out to be one or two accounts repeatedly hitting 0xC000006A from a device that has not been re-pointed at a current password.
# Find which accounts are generating the most wrong-password events right now
Get-WinEvent -FilterHashtable @{ LogName = 'Security'; Id = 4625 } -MaxEvents 1000 |
ForEach-Object {
$xml = [xml]$_.ToXml()
$data = $xml.Event.EventData.Data
$subStatus = ($data | Where-Object Name -eq 'SubStatus').'#text'
if ($subStatus -eq '0xc000006a') {
[pscustomobject]@{
Account = ($data | Where-Object Name -eq 'TargetUserName').'#text'
Source = ($data | Where-Object Name -eq 'IpAddress').'#text'
}
}
} | Group-Object Account, Source | Sort-Object Count -Descending | Select-Object -First 10
# Once identified, confirm current account state before resetting anything
Get-ADUser -Identity jdoe -Properties LockedOut, BadLogonCount, LastBadPasswordAttempt, PasswordLastSet
If the pattern turns out to be a genuine password-spray attempt — many accounts, one Status/Sub Status pair, one source, tight timing — treat it as a security event rather than a helpdesk ticket: block the source, force resets on any account that shows a matching successful 4624 afterward, and verify the account lockout policy engaged as configured.
How to Prevent It
Confirm the Logon and Account Lockout audit subcategories are enabled for failure auditing with auditpol /get /category:"Logon/Logoff" — this is the audit category 4625 belongs to. From there, alert on patterns rather than raw volume: many accounts failing with the same Sub Status from one source address in a short window, or one high-value account failing from a Source Network Address it has never used before.
Pair Logon Type with account sensitivity when building detections — a failed Type 4 or Type 5 logon attempt against a domain admin account is worth its own alert regardless of the failure code, since that account should rarely if ever be authenticating as a scheduled task or service. For the underlying authentication mechanics this event sits on top of, the Kerberos authentication post covers what a successful exchange looks like, and the Event 4771 post covers the Kerberos-specific counterpart that fires before a logon session is even attempted.
Quick Reference
| Code / Command | Meaning / Use |
|---|---|
0xC000006D | Generic invalid logon; check Sub Status for the real reason. |
0xC0000064 | Sub Status: the account does not exist. |
0xC000006A | Sub Status: the account exists but the password is wrong. |
0xC0000234 | Status: account locked out. |
0xC0000072 | Status: account disabled. |
0xC0000193 | Status: account expired. |
0xC0000071 | Status: password expired. |
0xC0000224 | Status: password must be changed at next logon. |
0xC000005E | Status: no logon servers available to service the request. |
Get-WinEvent -FilterHashtable @{LogName='Security';Id=4625} | Pull 4625 events from the Security log for triage. |
Search-ADAccount -LockedOut | List every currently locked-out account in one query. |
auditpol /get /category:"Logon/Logoff" | Confirm the Logon and Account Lockout subcategories are auditing failures. |
Final Thoughts
Event 4625 looks like a single, undifferentiated stream of failures until you separate it by Status, Sub Status, and Logon Type. Once it is, the same raw count of “failed logons this hour” turns into a small set of distinct problems: a stale credential, an intentionally disabled account, a genuinely unavailable domain controller, or an actual attacker — each needing a different response.
Read the Sub Status before the Status when the outer code is the generic 0xC000006D, and read the Logon Type before deciding how worried to be. A failed Type 2 at someone’s own desk is routine; the same failure code as a Type 3 against a service account from an address it has never used is not.
0xC000006D with Sub Status 0xC000006A is a bad password; 0xC0000234 is a lockout worth confirming before unlocking; 0xC000005E means go check domain controller availability, not the account.
Next: NTLM authentication and how a restricted NTLM policy can turn ordinary logons into a wall of Event 4625 entries that have nothing to do with a wrong password at all.