Failed logons are one of the most useful signals in Active Directory. A handful of them is usually someone fat-fingering a password. A flood of them across dozens of accounts from one source is a password spray in progress. The difference is right there in the Security event log — if you’ve turned on the right auditing and you know how to read Event ID 4625.
This guide gets you from “I’m not sure if we even log this” to “I can tell who failed to log on, from where, and why.” We’ll enable the correct audit policy, break down the fields in a 4625 event (especially the failure codes everyone ignores), and pull the events with PowerShell so you’re not clicking through Event Viewer one entry at a time.
The key event is 4625, “An account failed to log on.” Everything here revolves around capturing it and interpreting it.
First, turn on logon auditing
You can’t audit what you never enabled. Failed logon auditing is controlled by the Advanced Audit Policy, configured through Group Policy and applied to your domain controllers and member servers.
The path in a GPO:
Computer Configuration
> Policies
> Windows Settings
> Security Settings
> Advanced Audit Policy Configuration
> Audit Policies
> Logon/Logoff
> Audit Logon -> Success and Failure
For tracking failed logons you need Failure at minimum; enabling Success too gives you the full picture (and lets you confirm a legitimate logon eventually succeeded after some failures).
Verify what’s actually applied on a given server:
# Check the effective setting for the Logon subcategory
auditpol /get /subcategory:"Logon"
You want it reporting Success and Failure (or at least Failure) for the Logon subcategory. Apply the GPO to the Domain Controllers OU so all DCs audit consistently, and to member servers via their own policy.
Where failed logons get logged
This trips people up: the 4625 event lands on the machine that processed the authentication, which isn’t always where you’d expect.
Where to look for 4625
| Domain account, network logon | Often the domain controller that handled the authentication |
|---|---|
| Interactive logon at a workstation | The workstation's local Security log |
| RDP to a server | The target server's Security log (logon type 10) |
| Service or scheduled task | The server running the service |
Because a domain has multiple DCs and many servers, the only way to get a complete picture is to collect Security logs centrally — via Windows Event Forwarding or a SIEM — so you can search across all of them at once. Hunting on individual machines works for a single incident but doesn’t scale.
Reading an Event ID 4625
A 4625 event has more in it than most people read. Here are the fields that matter:
Key fields in Event 4625
| Account For Which Logon Failed | The target username and domain — who someone tried to log in as |
|---|---|
| Logon Type | How the logon was attempted (see table below) |
| Workstation Name | The source machine name, when available |
| Source Network Address | The source IP — the most useful field for tracing attacks |
| Failure Reason / Status / Sub Status | Why it failed, as text and as hex codes |
| Caller Process Name | The process that requested the logon, on local failures |
Logon types
The Logon Type tells you the nature of the attempt, which matters a lot for triage:
Common logon types
| 2 | Interactive — someone at the console / keyboard |
|---|---|
| 3 | Network — accessing a share or service over the network |
| 4 | Batch — scheduled task |
| 5 | Service — a service starting under an account |
| 7 | Unlock — unlocking a locked session |
| 8 | NetworkCleartext — credentials sent in cleartext (e.g. some basic auth) |
| 10 | RemoteInteractive — RDP / Terminal Services |
A burst of type 3 failures from one IP across many usernames is a network-based spray. A type 10 failure spike is someone hammering RDP. Knowing the type points you at the right exposure.
Failure codes — the part everyone skips
The Status and Sub Status hex codes are where the real story is. The on-screen “Failure Reason” text is vague; the sub-status code is precise.
Common 4625 status / sub-status codes
| 0xC000006A | Wrong password (the username exists) |
|---|---|
| 0xC0000064 | Username does not exist |
| 0xC0000234 | Account is currently locked out |
| 0xC0000072 | Account is disabled |
| 0xC0000071 | Password has expired |
| 0xC0000070 | Logon not allowed from this workstation (workstation restriction) |
| 0xC000006F | Logon outside of allowed hours / time restriction |
| 0xC0000193 | Account has expired |
This distinction drives your response. Many 0xC0000064 failures (nonexistent usernames) from one source is classic spraying — the attacker is guessing account names. Repeated 0xC000006A (bad password) against valid accounts is brute force against known users. A single 0xC0000234 is just someone who already locked themselves out.
Pulling 4625 events with PowerShell
Event Viewer is fine for one event. For analysis, use Get-WinEvent.
# Get the last 50 failed logons on this server
Get-WinEvent -FilterHashtable @{ LogName = 'Security'; Id = 4625 } -MaxEvents 50 |
Select-Object TimeCreated, Message
Filter to a time window — for example failed logons in the last 24 hours:
$start = (Get-Date).AddDays(-1)
Get-WinEvent -FilterHashtable @{ LogName='Security'; Id=4625; StartTime=$start } |
Select-Object TimeCreated, Message
Group failures by source IP to spot an attacker quickly:
# Count failed logons per source IP in the last day
$start = (Get-Date).AddDays(-1)
Get-WinEvent -FilterHashtable @{ LogName='Security'; Id=4625; StartTime=$start } |
ForEach-Object { ([xml]$_.ToXml()).Event.EventData.Data |
Where-Object { $_.Name -eq 'IpAddress' } | Select-Object -ExpandProperty '#text' } |
Group-Object | Sort-Object Count -Descending |
Select-Object Count, Name
That last one is the workhorse for an active incident — it turns thousands of raw events into a short list of source IPs ranked by how many failures each generated.
Triage: attack or accident?
When you see failed logons, ask three questions in order:
- How many, and how fast? A few over an afternoon is noise. Hundreds in minutes is an event.
- One account or many? One account failing repeatedly is often a stuck saved credential or a forgotten password. Many different accounts from one source is spraying.
- Where from? An internal admin subnet is likely benign. An unexpected public IP, or a server that has no business authenticating users, is a red flag.
Keep the evidence
Audit data only helps if it survives long enough to use.
Failed logon auditing checklist
- Advanced Audit Policy > Logon/Logoff > Audit Logon set to Failure (and Success)
- Advanced audit policy forced to override the legacy basic categories
- Policy applied to the Domain Controllers OU and to member servers
- Security log size increased so events aren't overwritten too fast
- Security logs forwarded to a central collector / SIEM
- A documented way to group 4625 by source IP and status code
For a broader view of which audit categories belong on a domain controller, the auditing section of the Windows Server hardening checklist lines up the full set worth enabling.
Wrapping up
Auditing failed logons in Active Directory is two skills: turning on the right policy so 4625 events are actually captured, and reading those events properly once you have them. The fields that matter most are the logon type, the source network address, and the sub-status code — together they tell you whether you’re looking at a typo, a stale saved credential, or a spray hitting your domain.
Set the advanced audit policy on your DCs and servers, forward the logs somewhere central, and keep the status-code table handy. When something does go wrong, you’ll be reading a clear story instead of guessing.