You're staring at SEC_E_INVALID_HANDLE (0x80090301) and nothing's talking to anything. Frustrating, I know. Let's kill it in the next five minutes.
The fix that clears 0x80090301 nine times out of ten
On the machine throwing the error, restart the security-related services in this exact order. This is my go-to when a server suddenly can't authenticate anything after a long uptime.
net stop w3svc
net stop http /y
net stop cryptsvc
net start cryptsvc
net start http
net start w3svc
Not running IIS? Skip w3svc and http. Just cycle cryptsvc, LanmanWorkstation, and Kerberos:
net stop kdc
net stop lanmanworkstation
net start lanmanworkstation
net start kdc
On client workstations, also flush the SSPI cache by rebooting. I know, "have you tried turning it off and on" — but SSPI handles live in process memory and survive logoff, so a reboot is the only reliable reset when you don't know which process is holding the bad handle.
If the error's coming from a .NET app, add this to your connection/request code before reusing any SafeHandle:
if (handle == null || handle.IsInvalid || handle.IsClosed)
{
context = new SafeDeleteContext();
// re-acquire from SSPI
}
Never reuse a handle after the using block ends. That's how 90% of these get created in the first place.
Why that actually fixes it
SSPI — the Security Support Provider Interface sitting under Kerberos, NTLM, Schannel, and Negotiate — hands out opaque handles for every security context. Those handles are process-local. When a process exits, the handle dies with it. But if the same handle value gets stored somewhere long-lived (registry, config file, static variable, cached HTTP connection pool) and another process tries to use it, Windows doesn't recognize it and throws SEC_E_INVALID_HANDLE.
Restarting the services forces everything to renegotiate fresh handles from scratch. There's no way to "repair" a dead SSPI handle — you can only discard it and get a new one. That's why cycling the stack works and why half the KB articles online telling you to run sfc /scannow waste your time.
Less common variations
Kerberos ticket expired but the service kept the handle
Long-running services with cached Kerberos handles blow up when the TGT expires. Default is 10 hours. If your app's been up 11 hours and only fails on the first request after that, this is it. Fix: force re-authentication. On the client side:
klist purge
And in code, don't hold NetworkCredential objects forever — rebuild them per request or use CredentialCache.DefaultNetworkCredentials which fetches fresh each time.
TLS/SSL via Schannel
Saw this exact error last month on a Windows Server 2019 box running SQL Server 2019 with Force Encryption on. The client was a legacy .NET 4.6.2 app reusing SslStream handles across reconnects. TLS session resumption broke after the server's cert was renewed. Fix was adding ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 and disabling session caching:
ServicePointManager.Expect100Continue = false;
ServicePointManager.CheckCertificateRevocationList = false;
ServicePointManager.ServerCertificateValidationCallback = null;
Also check HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL for stale cipher suite overrides. If someone hand-edited this key for a compliance scan and left garbage in it, Schannel will hand out invalid handles on purpose.
IIS application pool identity mishaps
If 0x80090301 only appears in IIS logs (event ID 500 with substatus 0), the app pool's virtual account probably lost its SPN. Check:
setspn -L DOMAIN\svc_apppool
Missing or duplicate SPNs cause Negotiate to fail with an invalid handle because the LSA can't match the token. Re-register:
setspn -D HTTP/web.contoso.com DOMAIN\svc_apppool
setspn -A HTTP/web.contoso.com DOMAIN\svc_apppool
LDAP bind failures after a password change
Service accounts with expired passwords throw this in System.DirectoryServices calls. The LDAP client caches the credential handle and never reauths. Update the password in the service config AND restart the service — just changing the password in AD isn't enough because the cached handle is still pointing at the old context.
Prevention
- Rotate long-lived services weekly if you can't fix the code. Scheduled task,
Restart-Service, done. Ugly but effective. - Set Kerberos TGT lifetime lower than your longest request window if you're writing code.
MaxTicketAgeandMaxRenewAgeunderHKLM\SYSTEM\CurrentControlSet\Control\Lsa\Kerberos\Parameters. - Never serialize an SSPI handle to disk, env var, or config. Full stop. It's process-local by design.
- Watch for duplicate SPNs after any server migration.
setspn -Xfinds them all at once. - If you're running .NET, wrap every security context in
usingand don't cacheSafeHandlederivatives beyond a single request scope. - Enable Kerberos event logging on domain controllers temporarily if you keep seeing this. Set
HKLM\SYSTEM\CurrentControlSet\Control\Lsa\Kerberos\Parameters\LogLevelto 1, reproduce, then turn it off — it's noisy.
Most 0x80090301 hits trace back to a handle outliving the process that created it. Kill the process, restart the services, fix the code that cached it. That's the whole game.