How to Configure Kerberos Constrained Delegation on Windows Server 2025
Kerberos delegation allows a service to impersonate a user when accessing another backend service on that user’s behalf. This capability is fundamental to multi-tier application architectures — for example, a web front-end that must authenticate to a SQL Server or file share using the identity of the end user. Windows Server 2025 supports three delegation models: unconstrained delegation (legacy, dangerous), traditional constrained delegation (KCD), and resource-based constrained delegation (RBCD). Understanding the security implications and operational differences between these models is essential for any Active Directory administrator. This tutorial covers all three, with detailed PowerShell examples for configuration, Kerberos ticket verification using klist, S4U2Self and S4U2Proxy protocol internals, and troubleshooting common delegation failures.
Prerequisites
- Windows Server 2025 Active Directory domain at functional level 2008 R2 or higher (2025 recommended for full RBCD support)
- Domain Admin credentials for configuring traditional KCD
- RSAT Active Directory tools installed:
Install-WindowsFeature RSAT-AD-PowerShell - Understanding of Kerberos SPN (Service Principal Name) registration
- Test service accounts and application servers for validation
- PowerShell 5.1 or later
Step 1: Understand the Three Delegation Types
Before configuring delegation, it is critical to understand what each type permits and the associated security risk.
Unconstrained Delegation — Avoid
When a computer or service account is configured for unconstrained delegation, Active Directory includes the user’s full TGT (Ticket-Granting Ticket) in the Kerberos service ticket. The delegating service can use this TGT to authenticate to any service in the domain as the impersonated user. This is extremely dangerous because any account or computer with unconstrained delegation that can be compromised gives an attacker a path to domain compromise through the printer bug or similar coercion attacks.
# Audit all accounts with unconstrained delegation enabled — these are high-risk targets
Import-Module ActiveDirectory
# Find computers with unconstrained delegation (TrustedForDelegation = True)
Get-ADComputer -Filter { TrustedForDelegation -eq $true -and PrimaryGroupID -ne 516 } `
-Properties TrustedForDelegation, OperatingSystem |
Select-Object Name, OperatingSystem, TrustedForDelegation |
Sort-Object Name
# Find user/service accounts with unconstrained delegation
Get-ADUser -Filter { TrustedForDelegation -eq $true } `
-Properties TrustedForDelegation, ServicePrincipalName |
Select-Object SamAccountName, ServicePrincipalName, TrustedForDelegation
Constrained Delegation (KCD) — Traditional
Traditional KCD restricts which target services a delegating account may impersonate users to. The allowed targets are stored in the msDS-AllowedToDelegateTo attribute. KCD requires Domain Admin rights to configure and uses either the Kerberos only (pure Kerberos) or any authentication protocol (Protocol Transition, enabling S4U2Self) mode.
Resource-Based Constrained Delegation (RBCD)
RBCD inverts the trust model. Instead of the front-end service specifying where it can delegate to, the back-end resource specifies which accounts are permitted to delegate to it. This is stored in the target computer’s msDS-AllowedToActOnBehalfOfOtherIdentity attribute and can be set by the machine account owner — not just Domain Admins. RBCD is the preferred model in Windows Server 2025 environments.
Step 2: Configure Traditional Constrained Delegation (KCD)
In this scenario, a web application service account (svc-webapp) needs to impersonate users when accessing a SQL Server back-end (sql01.contoso.com).
Import-Module ActiveDirectory
$frontEndAccount = "svc-webapp"
$targetSPN = "MSSQLSvc/sql01.contoso.com:1433"
# Method 1: Kerberos-only constrained delegation
# Requires the front-end service to receive a Kerberos ticket from the user
Set-ADUser -Identity $frontEndAccount `
-Add @{ 'msDS-AllowedToDelegateTo' = $targetSPN }
# Ensure TrustedToAuthForDelegation is NOT set for Kerberos-only mode
Set-ADAccountControl -Identity $frontEndAccount `
-TrustedForDelegation $false `
-TrustedToAuthForDelegation $false
# Verify the delegation attribute
Get-ADUser -Identity $frontEndAccount -Properties 'msDS-AllowedToDelegateTo', TrustedToAuthForDelegation |
Select-Object SamAccountName, TrustedToAuthForDelegation, 'msDS-AllowedToDelegateTo'
If the front-end service receives non-Kerberos authentication (e.g., NTLM or forms auth) and must still delegate to the back-end, enable Protocol Transition via the any authentication protocol mode:
# Method 2: Protocol Transition (any authentication protocol)
# TrustedToAuthForDelegation enables S4U2Self — service can request a Kerberos ticket
# for a user even when the user did not authenticate with Kerberos
Set-ADUser -Identity $frontEndAccount `
-Add @{ 'msDS-AllowedToDelegateTo' = $targetSPN }
Set-ADAccountControl -Identity $frontEndAccount `
-TrustedToAuthForDelegation $true
# Verify
Get-ADUser -Identity $frontEndAccount `
-Properties 'msDS-AllowedToDelegateTo', TrustedToAuthForDelegation |
Select-Object SamAccountName, TrustedToAuthForDelegation,
@{ N='DelegateTo'; E={ $_.'msDS-AllowedToDelegateTo' -join ', ' } }
Step 3: Configure Resource-Based Constrained Delegation (RBCD)
RBCD is configured on the target (back-end) resource. The target computer’s msDS-AllowedToActOnBehalfOfOtherIdentity attribute is populated with the security descriptor of the accounts permitted to delegate to it.
Import-Module ActiveDirectory
$frontEndComputer = "webapp01" # Machine account of the front-end service
$backEndComputer = "sql01" # Machine account of the back-end resource
# Get the front-end computer's SID
$frontEndSID = (Get-ADComputer -Identity $frontEndComputer).SID
# Build a security descriptor allowing the front-end to delegate to the back-end
$rawSD = New-Object Security.AccessControl.RawSecurityDescriptor(
"O:BAD:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;$frontEndSID)"
)
$sdBytes = New-Object byte[]($rawSD.BinaryLength)
$rawSD.GetBinaryForm($sdBytes, 0)
# Set RBCD on the back-end resource
Set-ADComputer -Identity $backEndComputer `
-PrincipalsAllowedToDelegateToAccount (Get-ADComputer -Identity $frontEndComputer)
# Verify the RBCD configuration
Get-ADComputer -Identity $backEndComputer `
-Properties 'msDS-AllowedToActOnBehalfOfOtherIdentity' |
Select-Object Name, 'msDS-AllowedToActOnBehalfOfOtherIdentity'
# Preferred shorthand using PrincipalsAllowedToDelegateToAccount
$backEnd = Get-ADComputer -Identity $backEndComputer
$frontEnd = Get-ADComputer -Identity $frontEndComputer
Set-ADComputer $backEnd -PrincipalsAllowedToDelegateToAccount $frontEnd
# Confirm
Get-ADComputer $backEnd -Properties PrincipalsAllowedToDelegateToAccount |
Select-Object -ExpandProperty PrincipalsAllowedToDelegateToAccount
Step 4: Understand S4U2Self and S4U2Proxy
Traditional KCD with Protocol Transition and RBCD both rely on two Kerberos extensions defined in RFC 4120 and Microsoft extensions:
- S4U2Self (Service for User to Self): Allows a service to obtain a Kerberos service ticket for a user to itself, without requiring the user to authenticate with Kerberos. The resulting ticket is forwardable only if the service is trusted for delegation.
- S4U2Proxy (Service for User to Proxy): Allows a service to use a forwardable service ticket (obtained via S4U2Self or directly from the user) to request a service ticket to a target backend on behalf of the user.
# Verify Kerberos tickets on a server running the front-end application
# Run on the application server as the service account
# List all cached Kerberos tickets
klist
# List tickets for a specific logon session
klist tickets
# Purge cached tickets and request fresh ones (useful during testing)
klist purge
# On a client machine, verify that a forwarded service ticket was issued
klist get MSSQLSvc/sql01.contoso.com:1433
Step 5: Service Account SPN Registration
Delegation only works when the delegating service account has correctly registered SPNs. Missing or duplicate SPNs are a leading cause of delegation failures.
# Register SPNs for a service account
$serviceAccount = "contososvc-webapp"
$hostname = "webapp01.contoso.com"
$shortName = "webapp01"
# Register HTTP SPNs (required for web applications using Kerberos auth)
setspn -A "HTTP/$hostname" $serviceAccount
setspn -A "HTTP/$shortName" $serviceAccount
# Verify SPNs
setspn -L $serviceAccount
# Check for duplicate SPNs across the domain (duplicate SPNs break Kerberos)
setspn -X
# PowerShell equivalent for SPN audit
Get-ADUser -Filter { ServicePrincipalName -like "HTTP/*" } -Properties ServicePrincipalName |
Select-Object SamAccountName, ServicePrincipalName |
ForEach-Object {
[PSCustomObject]@{
Account = $_.SamAccountName
SPN = $_.ServicePrincipalName -join '; '
}
}
Step 6: Troubleshoot KRB_AP_ERR_BADMATCH and Common Delegation Errors
The KRB_AP_ERR_BADMATCH error occurs when the service ticket presented to a server does not match the server’s keytab or AD account. Common causes include SPN mismatches, stale tickets, or clock skew greater than five minutes.
# Check domain controller time synchronization (Kerberos requires < 5 min clock skew)
w32tm /query /status
w32tm /resync /force
# Check Kerberos event log for delegation failures
Get-WinEvent -LogName "System" -FilterXPath `
"*[System[(EventID=3 or EventID=4 or EventID=14)]]" |
Select-Object TimeCreated, Id, Message |
Select-Object -First 20
# Check Security event log for Kerberos failures (Event 4769)
Get-WinEvent -ComputerName $env:COMPUTERNAME -FilterHashtable @{
LogName = 'Security'
Id = 4769
StartTime = (Get-Date).AddHours(-2)
} | ForEach-Object {
[PSCustomObject]@{
Time = $_.TimeCreated
ServiceName = $_.Properties[2].Value
ClientAddress = $_.Properties[6].Value
FailureCode = $_.Properties[4].Value
}
} | Where-Object { $_.FailureCode -ne "0x0" } | Sort-Object Time -Descending
# Common failure codes:
# 0x18 = KDC_ERR_PREAUTH_FAILED (wrong password or account locked)
# 0x20 = KDC_ERR_TGT_REVOKED (account disabled or logon restriction)
# 0x1F = KRB_AP_ERR_SKEW (clock skew too large)
# 0x3C = KDC_ERR_BADOPTION (delegation not configured correctly)
If you receive delegation errors in application logs, use Wireshark or Network Monitor to capture Kerberos traffic on port 88 and inspect AS-REQ/TGS-REQ message exchanges. The etype fields in the ticket reveal whether AES or RC4 encryption is being negotiated — RC4 downgrade can indicate attack or misconfiguration.
# Verify that the account is NOT in the Protected Users security group
# (Protected Users blocks all Kerberos delegation)
$account = "svc-webapp"
$protectedUsers = Get-ADGroupMember -Identity "Protected Users" -Recursive |
Select-Object -ExpandProperty SamAccountName
if ($protectedUsers -contains $account) {
Write-Warning "Account $account is a member of Protected Users — delegation is blocked."
} else {
Write-Host "Account $account is NOT in Protected Users — delegation should work."
}
Conclusion
Kerberos constrained delegation is a foundational Active Directory capability that, when configured correctly, enables secure service impersonation without exposing domain-wide credential risk. On Windows Server 2025, resource-based constrained delegation should be the preferred model for all new application deployments because it decentralizes control to resource owners, works across forest trusts without requiring inter-forest domain admin coordination, and integrates cleanly with modern cloud-hybrid architectures. Always audit unconstrained delegation configurations in your environment, remove them wherever possible, and validate delegation chains using klist and Kerberos event logging. Proper SPN registration and clock synchronization are prerequisites that resolve the majority of real-world delegation failures before more complex debugging is required.