Programming languages

The SSL connection could not be established (.NET): fixes

By · Last verified

Error messageThe 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_2 and -tls1_3; on Linux, read /etc/ssl/openssl.cnf for MinProtocol and CipherString.
  • Fix: upgrade the server's TLS configuration; do not pin SslProtocols in 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

  1. Microsoft Learn: Troubleshoot SslStream authentication issues
  2. Microsoft Learn: Cross-platform cryptography (trusted root locations on Linux)
  3. Microsoft Learn: SslClientAuthenticationOptions.CertificateChainPolicy
  4. Microsoft Learn: HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
  5. dotnet/runtime: System.Net.Http Strings.resx