Programming languages
The SSL connection could not be established (.NET): fixes
The SSL connection could not be established, see inner exception.System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception. means the TLS handshake failed; HttpClient uses this wrapper for every TLS problem. The inner exception holds the cause: a rejected certificate, a name mismatch, no common protocol or a dropped connection. Fix that cause rather than using DangerousAcceptAnyServerCertificateValidator.
What the error means
The inner exception, not this wrapper, holds the diagnosis. HttpClient (through SocketsHttpHandler and SslStream) raises this HttpRequestException whenever authentication fails. The text comes from the net_http_ssl_connection_failed resource in .NET. Below it you will find one of these inner exceptions, which are the real diagnosis:
AuthenticationException: The remote certificate is invalid according to the validation procedure: RemoteCertificateChainErrors
AuthenticationException: The remote certificate is invalid according to the validation procedure: RemoteCertificateNameMismatch
AuthenticationException: The remote certificate is invalid because of errors in the certificate chain: UntrustedRoot
AuthenticationException: Authentication failed because the remote party has closed the transport stream.
AuthenticationException: Authentication failed because the remote party sent a TLS alert: 'HandshakeFailure'.
Win32Exception (0x80090326): The message received was unexpected or badly formatted.
Microsoft's SslStream troubleshooting guide points out that .NET relies on the operating system: Schannel on Windows, OpenSSL on Linux. The same code can therefore fail on one platform and work on another, with different inner messages.
Causes, most common first
1. The certificate chain is not trusted
RemoteCertificateChainErrors with UntrustedRoot or PartialChain: the server uses a private CA, a TLS-inspecting proxy re-signs the traffic, or the server does not send its intermediate certificate.
- Confirm: check the chain from outside with the SSL checker or chain builder. If the public view is fine, the client's trust store is missing a root, typically inside a Linux container.
- Fix: add the root to the platform store (commands below), or trust it for one client with
CertificateChainPolicy. Missing intermediates must be fixed on the server (missing intermediate certificate).
2. The host name does not match
RemoteCertificateNameMismatch: you call an IP address, an internal alias or the bare domain while the certificate covers other names.
- Confirm:
openssl s_client -connect example.com:443 -servername example.com </dev/null | openssl x509 -noout -ext subjectAltName. - Fix: call a name the certificate covers or reissue the certificate. More detail: .NET remote certificate is invalid.
3. No common protocol or cipher suite
The inner exception shows remote party has closed the transport stream, a HandshakeFailure alert, or on Windows a Schannel error. Causes include a server stuck on TLS 1.0, a server that requires TLS 1.3 while Windows 10 tops out at TLS 1.2 in Schannel, or a container whose /etc/ssl/openssl.cnf disables what the server offers.
- Confirm:
openssl s_client -connect example.com:443 -servername example.com -tls1_2and-tls1_3; on Linux, read/etc/ssl/openssl.cnfforMinProtocolandCipherString. - Fix: upgrade the server's TLS configuration; do not pin
SslProtocolsin code.
4. A firewall or proxy drops the connection
The same request works from another network, or only fails behind the corporate proxy. Configure the proxy for HttpClient (HttpClient.DefaultProxy or the HTTPS_PROXY variable) and trust the proxy's root if it inspects TLS (corporate SSL inspection).
5. Client certificate and Windows specifics
With mutual TLS, a client certificate whose private key the process cannot use fails the handshake; Microsoft documents 0x8009030E: No credentials are available in the security package for certificates with ephemeral keys on Windows. Large certificate chains can also exceed Schannel's handshake message limit (0x80090326). See Windows Schannel errors.
Fix commands by platform
Most fixes add the missing root to the trust store that .NET reads on that platform.
Windows (elevated PowerShell):
Import-Certificate -FilePath C:\certs\company-root.cer -CertStoreLocation Cert:\LocalMachine\Root
Debian / Ubuntu images:
COPY company-root.crt /usr/local/share/ca-certificates/company-root.crt
RUN update-ca-certificates
RHEL / Alpine: update-ca-trust after copying into /etc/pki/ca-trust/source/anchors/ (RHEL), or update-ca-certificates (Alpine). On any Linux you can also point SSL_CERT_FILE at a PEM bundle; Microsoft's docs confirm .NET honours SSL_CERT_FILE and SSL_CERT_DIR.
macOS:
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain company-root.cer
In code, one client only (.NET 7+):
using System.Net.Http;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
var root = X509Certificate2.CreateFromPemFile("company-root.pem");
var handler = new SocketsHttpHandler
{
SslOptions = new SslClientAuthenticationOptions
{
CertificateChainPolicy = new X509ChainPolicy
{
TrustMode = X509ChainTrustMode.CustomRootTrust,
CustomTrustStore = { root },
},
},
};
using var client = new HttpClient(handler);
The host name is still checked, and every other root is rejected for this client, which is exactly what you want for an internal service.
Don't disable certificate verification
handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator; or a callback that returns true accepts any certificate, including one forged by an attacker on the network path. Microsoft's API documentation describes it as a pattern for test scenarios and notes that its name exists so tools can flag the danger. In production it exposes bearer tokens, passwords and API data.
The safer alternatives are the ones above: install the root where the platform looks for it, use CertificateChainPolicy with CustomRootTrust for a single client, fix the server chain, and correct host names. If you need diagnostics, write a callback that logs sslPolicyErrors and chain.ChainStatus and still returns sslPolicyErrors == SslPolicyErrors.None.
Verify the fix
using var client = new HttpClient();
var response = await client.GetAsync("https://example.com/");
Console.WriteLine($"{(int)response.StatusCode} {response.Version}");
The request should return a status code with no HttpRequestException. Run it on the same OS image as production, because Windows and Linux trust stores differ. Re-check the server in the SSL checker, and see certificate chains explained if the inner exception mentioned PartialChain.
FAQ
Where do I find the inner exception?
Log ex.ToString() instead of ex.Message; it prints the whole chain of exceptions. In code, walk ex.InnerException until it is null. The innermost exception, often an AuthenticationException or a Win32Exception with an error code, names the real cause.
It works on my Windows machine but fails in a Linux container. Why?
Windows .NET uses the Windows certificate store and Schannel; Linux .NET uses OpenSSL and the container's CA bundle. A corporate root deployed to Windows by Group Policy is not in the container. Add it to the image with update-ca-certificates, or point SSL_CERT_FILE at a bundle that contains it. The container's /etc/ssl/openssl.cnf can also disable protocols or cipher suites that the server needs.
Should I set SslProtocols explicitly to fix it?
Usually not. Microsoft recommends letting the operating system choose the TLS version. Pinning SslProtocols.Tls12 can break TLS 1.3 later. Only restrict protocols when compliance requires it, and never include SSL 3 or TLS 1.0.
What does Authentication failed because the remote party has closed the transport stream mean?
The server or a device in between closed the TCP connection during the handshake instead of answering. Typical causes are a protocol or cipher mismatch, a missing SNI name, or a firewall. Test with openssl s_client -connect host:443 -servername host -tls1_2 and -tls1_3 to see which versions the server accepts.
Sources
- Microsoft Learn: Troubleshoot SslStream authentication issues
- Microsoft Learn: Cross-platform cryptography (trusted root locations on Linux)
- Microsoft Learn: SslClientAuthenticationOptions.CertificateChainPolicy
- Microsoft Learn: HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
- dotnet/runtime: System.Net.Http Strings.resx