Error message
The remote certificate was rejected by the provided RemoteCertificateValidationCallback.
Stable text across .NET Core and .NET 5–10; HttpClient wraps it in "The SSL connection could not be established, see inner exception." The SslPolicyErrors value passed to the callback tells you whether .NET itself also found a problem.
.NET SslStream / HttpClient — a custom validation callback returned false
What it means
The application installed its own certificate validation callback (ServerCertificateCustomValidationCallback or RemoteCertificateValidationCallback), and that callback returned false. .NET's built-in checks may have passed; your code decided to reject the certificate. Typical with certificate or key pinning after the server's certificate was renewed or rotated.
Why it happens
The pinned certificate was replaced
commonPinning a certificate thumbprint breaks at every renewal (now every few months with public CAs); the new certificate has a new hash.
The callback's own logic rejects valid certificates
commonChecks for an exact issuer name, subject or thumbprint list that no longer match, or conditions that ignore the errors argument.
The callback correctly rejects a real problem
occasionalA callback that returns errors == SslPolicyErrors.None rejects untrusted, expired or mismatched certificates; the cause then is the certificate itself.
How to fix it
- 1.
Log what the callback sees
Record SslPolicyErrors, subject, SHA-256 thumbprint and expiry inside the callback, then update the pin list or fix the certificate.
csharp ServerCertificateCustomValidationCallback = (request, cert, chain, errors) => { var thumbprint = cert!.GetCertHashString(HashAlgorithmName.SHA256); logger.LogWarning("TLS {Host}: errors={Errors} subject={Subject} sha256={Thumb} notAfter={NotAfter:u}", request.RequestUri?.Host, errors, cert.Subject, thumbprint, cert.NotAfter); return errors == SslPolicyErrors.None && pinnedThumbprints.Contains(thumbprint); }, - 2.
Pin the public key, and keep a backup pin
SPKI pins stay valid across renewals that reuse the key; always pin at least one backup key so a rotation does not lock clients out.
bash # Pin the public key (SPKI) rather than the certificate: it survives renewals with the same key openssl s_client -connect api.example.com:443 -servername api.example.com </dev/null 2>/dev/null \ | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | base64