Event 4771 — Kerberos Pre-Authentication Failed
Event 4771 fires every time a domain controller refuses to hand out a Ticket Granting Ticket, and the Failure Code field is the only thing that tells you whether that refusal is a bad password, a disabled account, a brute-force attempt, or just normal Kerberos negotiation you can safely ignore.
A hex sub-status from RFC 4120 that tells you exactly why the KDC refused the TGT request.
The encrypted timestamp a client must produce before the KDC will trust it enough to issue a TGT.
Key Distribution Center — the domain controller service that issues TGTs and logs 4771 when it won’t.
Introduction
Someone opens Event Viewer on a domain controller, filters the Security log to Failure Audit, and finds thousands of Event ID 4771 entries from the last hour alone. A SIEM rule built around “Kerberos pre-authentication failed” has been firing all week. The reflex is to treat every one of them as a login attack in progress, but that reflex burns out an on-call rotation fast, because a healthy domain generates 4771 events constantly as part of ordinary Kerberos chatter.
The event by itself does not tell you whether you are looking at an expired password, a locked-out service account retrying every five minutes forever, a genuine password-spray attempt, or a completely normal part of the Kerberos handshake that happens to log as a “failure.” The Failure Code field is what turns the flood into something you can triage, and that is the field most admins never open the event far enough to read.
What You’re Seeing
On the domain controller’s Security log, a typical 4771 renders with these fields — this one from a straightforward bad-password attempt:
# Security log on the DC, Event ID 4771, Task Category: Kerberos Authentication Service
A Kerberos authentication ticket (TGT) request failed.
Account Information:
Security ID: CORP\jdoe
Account Name: jdoe
Service Information:
Service Name: krbtgt/CORP.EXAMPLE.COM
Network Information:
Client Address: ::ffff:10.10.4.52
Client Port: 49224
Additional Information:
Ticket Options: 0x40810010
Failure Code: 0x18
Pre-Authentication Type: 2
A large share of the flood, though, looks like this instead — same event ID, different failure code, and this one is not actually reporting a problem:
# Same DC, same event ID, different Failure Code
Additional Information:
Ticket Options: 0x40810010
Failure Code: 0x19
Pre-Authentication Type: 0
What It Actually Means
Think of pre-authentication as a bouncer checking ID before letting you queue for the ticket window. A plain Kerberos AS-REQ with no proof of identity would let anyone request a TGT for any account and start guessing the password offline against the reply. Pre-authentication closes that off: the client must first encrypt a timestamp with a key derived from the user’s password and send that along with the request. Only if the KDC can decrypt it correctly, proving the client already knows the password, does it hand over a TGT. Event 4771 is the KDC logging every time that check fails, for any of about forty distinct reasons defined in RFC 4120.
Most of the fields are straightforward: Account Name is who was trying to authenticate, Service Name is always some form of krbtgt/<REALM> because a TGT request is always addressed to the ticket-granting service itself, and Client Address/Client Port identify where the request came from — genuinely useful for spotting a source that shouldn’t be making these requests at all. The field that actually explains the failure is Failure Code, a hexadecimal value the DC copies straight out of the Kerberos protocol error it sent back to the client.
Pre-Authentication Type is worth a glance too: 2 means the client sent a normal encrypted-timestamp proof (standard password logon), 0 means the client sent no pre-authentication data at all, and 15/16/17 indicate a smart card / certificate-based exchange. A Pre-Authentication Type of 0 paired with Failure Code 0x19 is the signature of the normal two-round-trip negotiation described next — not an attack signature.
KDC_ERR_PREAUTH_REQUIRED in a network trace as one of the strings that is “part of typical Kerberos functions,” not a fault condition. When Windows logs that same exchange as a 4771 with Failure Code 0x19, it is recording routine protocol negotiation, most visibly from non-Windows or UNIX-interop Kerberos clients that don’t cache a preferred pre-authentication type between requests. Filter this one out before you start counting incidents.
How to Diagnose
Work from the log outward. Almost everything you need to triage a 4771 flood is visible without leaving PowerShell.
# 1. Pull recent 4771 events from the Security log and see what you're actually dealing with
Get-WinEvent -FilterHashtable @{ LogName = 'Security'; Id = 4771 } -MaxEvents 200 |
Select-Object TimeCreated, Id, Message
# 2. Group by Failure Code to separate noise (0x19) from real problems (0x18, 0x12, 0x25...)
Get-WinEvent -FilterHashtable @{ LogName = 'Security'; Id = 4771 } -MaxEvents 500 |
ForEach-Object {
$xml = [xml]$_.ToXml()
[pscustomobject]@{
Time = $_.TimeCreated
Account = ($xml.Event.EventData.Data | Where-Object Name -eq 'TargetUserName').'#text'
Client = ($xml.Event.EventData.Data | Where-Object Name -eq 'IpAddress').'#text'
Status = ($xml.Event.EventData.Data | Where-Object Name -eq 'Status').'#text'
}
} | Group-Object Status | Sort-Object Count -Descending
# 3. Check whether the 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, rather than checking one at a time
Search-ADAccount -LockedOut | Select-Object Name, SamAccountName, LockedOut
# 5. Confirm a KDC is reachable at all - useful if failures cluster around a specific site
nltest /dsgetdc:corp.example.com /force /kdc
# 6. If Failure Code is 0x25 (clock skew), confirm the offset before chasing anything else
w32tm /stripchart /computer:DC01.corp.example.com /samples:3 /dataonly
Common Causes
| Cause | How to Confirm | Fix |
|---|---|---|
Wrong password supplied (0x18, KDC_ERR_PREAUTH_FAILED) |
Failure Code 0x18 against a real account; often a mapped drive, scheduled task, or phone still holding an old password. |
Confirm with Get-ADUser -Identity <name> -Properties BadLogonCount, LastBadPasswordAttempt, then find and update the stale credential rather than just resetting the password again. |
Account does not exist (0x6, KDC_ERR_C_PRINCIPAL_UNKNOWN) |
Failure Code 0x6 against a username that returns nothing from Get-ADUser -Identity <name>. |
Usually a typo, a decommissioned account still referenced somewhere, or automated username-guessing; if the Client Address is external or unexpected, treat it as reconnaissance. |
Account disabled or otherwise revoked (0x12, KDC_ERR_CLIENT_REVOKED) |
Get-ADUser -Identity <name> -Properties Enabled returns False, or the account is locked out. |
Confirm the disable/lockout was intentional with Search-ADAccount -LockedOut; re-enable only if it was accidental, otherwise silence the alert for that known-disabled account. |
Password expired (0x17, KDC_ERR_KEY_EXPIRED) |
Search-ADAccount -PasswordExpired lists the account generating the events. |
Have the user change their password, or investigate why a service account’s non-expiring flag was not set if it’s a service identity. |
Normal Kerberos pre-authentication negotiation, not a failure (0x19, KDC_ERR_PREAUTH_REQUIRED) |
Failure Code 0x19 with Pre-Authentication Type 0, typically followed immediately by a successful request from the same client. |
No fix needed — this is the client’s first round-trip before it resends with a valid encrypted timestamp. Exclude Failure Code 0x19 from alerting thresholds so it stops masking real signal. |
Clock skew between client and DC exceeds five minutes (0x25, KRB_AP_ERR_SKEW) |
w32tm /stripchart /computer:<DC> /samples:3 /dataonly from the affected client shows an offset over five minutes. |
Fix the client’s time source, not the directory — see the Kerberos clock skew post for the full W32Time diagnostic path. |
| Genuine brute-force or password-spray attempt | High volume of Failure Code 0x18 or 0x6 against one account from a single Client Address, or the same failure code against many different accounts from one address in a short window. |
Lock down or block the source address, force a password reset on any targeted account, and check whether the account lockout threshold actually triggered — see the account lockouts post if it didn’t. |
Working Through a Fix
Start by separating the flood into buckets by Failure Code before you touch a single account — the script in step 2 of the diagnosis section does this in one pass. In most environments the majority of volume turns out to be 0x19 noise plus a handful of accounts repeatedly hitting 0x18 from a device nobody has re-pointed at a new password.
# Find which accounts are generating the most 0x18 (bad password) events right now
Get-WinEvent -FilterHashtable @{ LogName = 'Security'; Id = 4771 } -MaxEvents 1000 |
ForEach-Object {
$xml = [xml]$_.ToXml()
$status = ($xml.Event.EventData.Data | Where-Object Name -eq 'Status').'#text'
if ($status -eq '0x18') {
[pscustomobject]@{
Account = ($xml.Event.EventData.Data | Where-Object Name -eq 'TargetUserName').'#text'
Client = ($xml.Event.EventData.Data | Where-Object Name -eq 'IpAddress').'#text'
}
}
} | Group-Object Account, Client | Sort-Object Count -Descending | Select-Object -First 10
# Once you've identified the account, confirm current state before resetting anything
Get-ADUser -Identity jdoe -Properties LockedOut, BadLogonCount, LastBadPasswordAttempt, PasswordLastSet
For a stale-credential case — by far the most common source of a sustained 0x18 flood — the fix is finding whatever is still submitting the old password, not the account itself. A phone with cached Wi-Fi or ActiveSync credentials, a mapped drive with saved credentials in Windows Credential Manager, a scheduled task running under a service account whose password rotated, and an old RDP or VPN session are the usual suspects, in roughly that order of frequency.
Client Address and, where available, device inventory, and update or remove it there.
If the flood turns out to be a genuine password-spray or brute-force pattern — many distinct accounts, one Failure Code, one source address, tight timing — treat it as a security event rather than a helpdesk ticket: block the source at the firewall or conditional access layer, force resets on any account that shows a corresponding successful 4768 (meaning the guess eventually worked), and confirm your account lockout policy actually triggered where it should have.
How to Prevent It
Confirm Kerberos Authentication Service auditing is actually enabled with auditpol /get /subcategory:"Kerberos Authentication Service" — this is the audit subcategory 4771 belongs to, and it needs to be set to record failure events (the default in a modern baseline) or you get silence instead of signal. From there, tune your SIEM or log pipeline to exclude Failure Code 0x19 from volume-based alert thresholds specifically, since counting it alongside genuine failures is what makes “thousands of 4771s an hour” look alarming when most of it is protocol handshake noise.
For the codes that do matter, alert on patterns rather than single events: many accounts failing with the same code from one Client Address in a short window, or one high-value account failing repeatedly from an address it has never authenticated from before. Both are exactly the kind of thing Microsoft’s own monitoring recommendations for this event call out — tracking Client Address against an allow-list per account, and watching for Client Port values under 1024, which suggest an outbound connection from a service rather than a normal client. Pair this with the Kerberos authentication fundamentals if the pre-authentication step itself still feels opaque — this event only makes sense once you know what that step is checking for in the first place.
Quick Reference
| Code / Command | Meaning / Use |
|---|---|
0x6 | KDC_ERR_C_PRINCIPAL_UNKNOWN — the account doesn’t exist in the KDC’s database. |
0x12 | KDC_ERR_CLIENT_REVOKED — account disabled, locked out, or otherwise revoked. |
0x17 | KDC_ERR_KEY_EXPIRED — the password has expired. |
0x18 | KDC_ERR_PREAUTH_FAILED — wrong password (or a smart card mismatch). |
0x19 | KDC_ERR_PREAUTH_REQUIRED — normal negotiation; the client is about to retry correctly. Not a failure. |
0x25 | KRB_AP_ERR_SKEW — client and DC clocks differ by more than the 5-minute tolerance. |
Get-WinEvent -FilterHashtable @{LogName='Security';Id=4771} | Pull 4771 events from the Security log for triage. |
Search-ADAccount -LockedOut | List every currently locked-out account in one query. |
auditpol /get /subcategory:"Kerberos Authentication Service" | Confirm 4771 auditing is actually enabled. |
Final Thoughts
Event 4771 looks like an incident feed at a glance, and treating every occurrence as one will exhaust anyone monitoring it within a week. It is really a TGT request log with a status code attached, and that status code is doing almost all of the useful work — the difference between a stale phone, an expired password, routine protocol chatter, and an actual attacker guessing passwords is entirely in the Failure Code field.
Read the field before reacting to the volume. Filter out 0x19, watch 0x18 and 0x6 for patterns rather than counts, and let 0x25 send you straight to the clock rather than the directory.
0x19 is normal negotiation and belongs out of your alert thresholds; 0x18, 0x6, and 0x12 deserve a look at the account and Client Address; 0x25 means go check the clock, not the account.
Next: decoding Event 4625 logon failures and their sub-status codes, the broader interactive-logon counterpart to a 4771 that never gets as far as a TGT request in the first place.