Kerberos · Troubleshooting

Kerberos Constrained Delegation Not Working

Constrained delegation almost always breaks in the same place — a middle-tier service that is not actually trusted to hand off a client’s identity to the back-end it is trying to reach — and the KDC tells you so bluntly with KDC_ERR_BADOPTION.

Quick idea: Constrained delegation lets a front-end service present a user’s identity to a specific, named back-end service — nothing wider. If the middle-tier account is not explicitly trusted for that exact hop, the KDC refuses the ticket request outright with KDC_ERR_BADOPTION. Almost every “delegation doesn’t work” ticket traces back to one missing checkbox or one missing SPN in that trust list.
KDC_ERR_BADOPTION

The KDC’s refusal when a client requests a service ticket the requesting account isn’t allowed to delegate to.

msDS-AllowedToDelegateTo

The attribute holding the exact list of SPNs a front-end account may delegate to. Empty or wrong, and delegation fails.

Protocol transition

The optional “use any authentication protocol” mode that lets a service delegate for a user it authenticated by non-Kerberos means.

Introduction

A web application authenticates a user fine on its own front door, but the moment it tries to query a back-end SQL Server or file share on that user’s behalf, everything the user does comes back as if it were run by the application’s own service account instead. Or worse — the middle tier just throws an authentication exception the instant it tries the second hop. Nothing about the user’s password or group membership is wrong; the double-hop itself is being refused.

This is constrained delegation failing to do the one thing it exists for: letting service A present a specific user’s identity to service B, and only service B. Once you accept that the KDC is enforcing an explicit allow-list rather than something being broken in Active Directory generally, the fix narrows quickly to a short set of account-level settings.

What You’re Seeing

The error surfaces most clearly in a network capture of the TGS-REQ the middle tier sends when it tries to obtain a service ticket to the back-end on the user’s behalf. Microsoft Network Monitor labels the response plainly:

# Network Monitor trace, filtered on KerberosV5
KerberosV5:TGS Request Cname: middletier$  Realm: CORP.EXAMPLE.COM  Sname: http/backend01.corp.example.com
KerberosV5:KRB_ERROR  KDC_ERR_BADOPTION

Above the protocol layer, the symptom is usually plainer and less specific: the middle-tier application logs a generic Kerberos or “access denied” exception the instant it attempts the second hop, while direct authentication to the front-end works normally. If the back-end is SQL Server, this typically shows up as the connection succeeding but running under the application’s own service identity rather than the calling user’s — or an outright login failure if the back-end has no fallback path.

Key point: KDC_ERR_BADOPTION on its own only tells you the request was rejected, not why. It fires whether the front-end account has no delegation configured at all, has the wrong SPN in its allow-list, or is explicitly blocked from delegation entirely — you diagnose which one by checking the account, not by decoding the error further.

What It Actually Means

When a client authenticates to a front-end service, that service can request a service ticket to a second, back-end service on the client’s behalf — but only if Active Directory says it is allowed to. Constrained delegation encodes that permission as an explicit list: the msDS-AllowedToDelegateTo attribute on the front-end’s account (its computer account, or a dedicated service account) holds the exact SPNs of the back-end services it may present credentials to. When the front-end asks the KDC for a ticket to a service that is not on that list — or asks at all when no list exists — the KDC returns KDC_ERR_BADOPTION rather than silently degrading to some lesser form of access.

Two further account-level switches change whether the request even gets that far. TrustedForDelegation governs unconstrained delegation and is unrelated to the constrained case. TrustedToAuthForDelegation enables protocol transition — “Use any authentication protocol” in the GUI — which lets the front-end delegate for a user it did not itself authenticate via Kerberos (for example, a user who logged on with forms-based auth). Without it, the front-end must have obtained the user’s own Kerberos ticket first (“Use Kerberos only”). Separately, an account with AccountNotDelegated set, or a member of the Protected Users group, cannot be delegated for under any configuration — that flag exists specifically to make an account immune to delegation, so if it is set on the user (not the service), no amount of front-end configuration will fix the error.

How to Diagnose

Work from the account configuration outward before reaching for a network trace — most delegation failures are visible from PowerShell alone.

# 1. Confirm the front-end and back-end are in the same domain (constrained
# delegation does not work across domains or forests - RBCD is required there)
Get-ADComputer -Identity MIDDLETIER01 -Properties CanonicalName
Get-ADComputer -Identity BACKEND01 -Properties CanonicalName

# 2. Check the front-end account's delegation configuration - computer account case
Get-ADComputer -Identity MIDDLETIER01 -Properties TrustedToAuthForDelegation, msDS-AllowedToDelegateTo |
    Select-Object Name, TrustedToAuthForDelegation, msDS-AllowedToDelegateTo

# 3. Same check for a custom service account running the front-end service
Get-ADUser -Identity svc-middletier -Properties TrustedToAuthForDelegation, msDS-AllowedToDelegateTo, AccountNotDelegated |
    Select-Object Name, TrustedToAuthForDelegation, msDS-AllowedToDelegateTo, AccountNotDelegated

# 4. Confirm the calling user isn't explicitly blocked from being delegated
Get-ADUser -Identity jdoe -Properties AccountNotDelegated, MemberOf |
    Select-Object Name, AccountNotDelegated

# 5. Confirm a KDC is reachable at all before assuming this is a delegation problem
nltest /dsgetdc:corp.example.com /force /kdc

# 6. Inspect the tickets actually held by the process/session hitting the error
klist tickets
Practical note: An empty or $null msDS-AllowedToDelegateTo on the front-end account is the single most common finding here. If that attribute has never been set, the front-end has no constrained delegation permissions at all, regardless of anything configured on the back-end.

Common Causes

Cause How to Confirm Fix
Back-end SPN missing from the front-end’s allow-list msDS-AllowedToDelegateTo on the front-end account doesn’t include the exact SPN the middle tier is requesting (e.g. HOST/backend01.corp.example.com). Add the missing SPN to the allow-list: Set-ADComputer -Identity MIDDLETIER01 -Add @{'msDS-AllowedToDelegateTo'=@('HOST/backend01.corp.example.com')}.
Delegation never enabled on the front-end account The Delegation tab is absent or set to “Do not trust this computer/user for delegation” in Active Directory Users and Computers. Select Trust this user for delegation to specified services only and add the back-end SPN, or set msDS-AllowedToDelegateTo directly via PowerShell.
Front-end authenticated the user by a non-Kerberos method (forms auth, certificates) but protocol transition isn’t enabled TrustedToAuthForDelegation is False on the front-end account while the app relies on “Use any authentication protocol”. Enable protocol transition on the front-end account: Set-ADAccountControl -Identity MIDDLETIER01 -TrustedToAuthForDelegation $true.
Calling user account is exempt from delegation Get-ADUser jdoe -Properties AccountNotDelegated returns True, or the user is a member of Protected Users. This is by design for high-privilege accounts — do not clear it to work around the error. Use a different, lower-privilege account for the delegated path, or redesign the access pattern.
Front-end and back-end are in different domains or forests Constrained delegation SPN checks fail even though the list looks correct — the topology itself doesn’t support classic KCD. Switch to resource-based constrained delegation (RBCD) using -PrincipalsAllowedToDelegateToAccount on the back-end account instead.
Custom service account is missing the matching computer-account delegation entry The service account’s own delegation list looks correct, but the front-end server’s computer account has no delegation configured at all. Configure Trust this computer for delegation to specified services only (with Use any authentication protocol) on the front-end server’s computer account, in addition to the service account.
SPN registered on the wrong account, or duplicated setspn -Q HOST/backend01.corp.example.com shows it registered to an account other than the one the back-end service actually runs as, or to more than one account. Remove the duplicate/incorrect registration with setspn -D, then register it against the correct back-end service account.

Working Through a Fix

Start by establishing which kind of front-end account you have — a computer account running the service under a built-in identity (Network Service, Local System) or a dedicated service account — because the configuration lives on different objects in each case. For a computer account, both the delegation configuration and the SPN list live on the same object:

# View current state before changing anything
Get-ADComputer -Identity MIDDLETIER01 -Properties TrustedToAuthForDelegation, msDS-AllowedToDelegateTo

# Enable constrained delegation with protocol transition to the back-end HOST SPN
Set-ADAccountControl -Identity MIDDLETIER01 -TrustedToAuthForDelegation $true
Set-ADComputer -Identity MIDDLETIER01 -Add @{'msDS-AllowedToDelegateTo'=@('HOST/backend01.corp.example.com')}

For a custom service account, the delegation permission goes on the service account itself, but the front-end server’s own computer account also needs delegation enabled — a step that’s easy to miss because it feels redundant when the service account already has it configured:

# Service account: enable delegation and the allow-list
Set-ADAccountControl -Identity svc-middletier -TrustedToAuthForDelegation $true
Set-ADComputer -Identity MIDDLETIER01 -Add @{'msDS-AllowedToDelegateTo'=@('HOST/backend01.corp.example.com')}

# Front-end computer account: also needs "Trust this computer for delegation
# to specified services only" with "Use any authentication protocol"
Set-ADComputer -Identity MIDDLETIER01 -TrustedForDelegation $false
Set-ADAccountControl -Identity MIDDLETIER01 -TrustedToAuthForDelegation $true
Production note: Changing delegation settings on a domain-joined computer or service account is a security-sensitive change — it widens what that account can act as on the network. Add only the specific back-end SPN required, never a wildcard, and confirm with whoever owns the back-end service before making the change on a production account.

If the front-end and back-end genuinely sit in different domains or forests, none of the above will ever succeed — classic constrained delegation is restricted to a single domain by design. The supported path there is resource-based constrained delegation, configured from the back-end side instead:

# On the back-end account, allow the front-end's computer account to
# delegate to it - this is configured on BACKEND01, not MIDDLETIER01
Set-ADComputer -Identity BACKEND01 -PrincipalsAllowedToDelegateToAccount MIDDLETIER01$

How to Prevent It

Keep the msDS-AllowedToDelegateTo list as narrow as the application actually needs — one or two specific back-end SPNs, never a broad set added “just in case.” A wide delegation list is exactly the kind of lateral-movement path an attacker who compromises the front-end account will look for, so treat it with the same review discipline as a firewall rule change. Document which account holds delegation rights and to what, since this configuration is invisible from the application side and tends to be forgotten until the next migration breaks it.

When standing up a new middle tier, confirm the topology (same domain vs cross-domain) before choosing constrained delegation over RBCD — picking the wrong one early causes exactly this error weeks later when someone finally exercises the double-hop path in production. For the protocol fundamentals this all sits on top of, see the Kerberos authentication post; if the failure turns out to be about ticket decryption rather than delegation permissions, that’s the separate duplicate SPN failure mode.

Quick Reference

Command Use
Get-ADComputer -Identity <name> -Properties msDS-AllowedToDelegateToList the SPNs a computer account is currently allowed to delegate to.
Set-ADComputer -Identity <name> -Add @{'msDS-AllowedToDelegateTo'=@('HOST/<target>')}Add a back-end SPN to the allow-list.
Set-ADAccountControl -Identity <name> -TrustedToAuthForDelegation $trueEnable protocol transition (“Use any authentication protocol”).
Get-ADUser -Identity <name> -Properties AccountNotDelegatedCheck whether a user account is explicitly exempt from delegation.
Set-ADComputer -Identity <backend> -PrincipalsAllowedToDelegateToAccount <frontend>$Configure resource-based constrained delegation (cross-domain safe).
setspn -Q <SPN>Check which account an SPN is registered against, and whether it’s duplicated.
KDC_ERR_BADOPTIONThe KDC rejected a TGS request because the requesting account isn’t permitted to delegate to the target SPN.

Final Thoughts

KDC_ERR_BADOPTION reads as cryptic the first time you hit it, but it is the KDC doing exactly its job: refusing a delegation request that was never explicitly granted. The fix is rarely about Kerberos internals — it’s confirming which account is actually acting as the front-end, and whether its allow-list names the back-end service you expect.

Check the account configuration before reaching for a network trace, keep the allow-list narrow, and remember that an account marked non-delegable is a security control working as intended, not a bug to route around.

Key takeaway: Run Get-ADComputer -Identity <frontend> -Properties TrustedToAuthForDelegation, msDS-AllowedToDelegateTo first. An empty or missing back-end SPN in that list is the most common cause of KDC_ERR_BADOPTION; add it with Set-ADComputer -Add, enable TrustedToAuthForDelegation if the app needs protocol transition, and confirm the calling user isn’t flagged AccountNotDelegated before assuming the fix worked.
More troubleshooting

Next: when Kerberos authentication fails for a reason that looks similar but isn’t — ticket decryption breaking under KRB_AP_ERR_MODIFIED because of a duplicate SPN.