Skip to content
ecdsa.com
.NET SslStream / HttpClient.NET

Error message

The remote certificate is invalid because of errors in the certificate chain: UntrustedRoot

Also printed as

  • System.Security.Authentication.AuthenticationException: The remote certificate is invalid because of errors in the certificate chain: UntrustedRoot

The flags after the colon come from X509ChainStatusFlags and can be combined (for example "UntrustedRoot, RevocationStatusUnknown"). Self-signed server certificates also report UntrustedRoot. On Linux, .NET uses OpenSSL and the distribution's CA store; on Windows and macOS, the system certificate stores.

.NET SslStream / HttpClient — AuthenticationException (X509ChainStatusFlags.UntrustedRoot)

What it means

.NET built the server's certificate chain up to a root, and that root is not trusted by the platform's store (or the chain ends in a self-signed certificate that is not trusted). The certificate itself may be fine; the machine just does not trust the authority behind it. HttpClient reports it as the inner exception of "The SSL connection could not be established".

Why it happens

How to fix it

  1. 1.

    Linux: install the root into the system store

    .NET on Linux reads the OpenSSL store, so update-ca-certificates (or the distribution's equivalent) is enough; in containers, do it in the Dockerfile.

    bash
    # Linux (incl. containers): .NET uses the OpenSSL system store
    sudo cp corp-root-ca.pem /usr/local/share/ca-certificates/corp-root-ca.crt
    sudo update-ca-certificates
  2. 2.

    Windows and macOS: trust the root in the system store

    Import the root into LocalMachine\Root on Windows or the System keychain on macOS; .NET uses those stores.

    text
    # Windows (elevated PowerShell): trust the root machine-wide
    Import-Certificate -FilePath .\corp-root-ca.cer -CertStoreLocation Cert:\LocalMachine\Root
    # macOS
    sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain corp-root-ca.pem
  3. 3.

    Trust a private root for one client

    SocketsHttpHandler.SslOptions.CertificateChainPolicy with CustomRootTrust keeps full validation but anchors it in your root. A custom policy enables online revocation checking by default, so set RevocationMode to match what your CA publishes (in our test, private certificates without CRL/OCSP failed with RevocationStatusUnknown until NoCheck was set).

    csharp
    // Trust one private root for this client only; chain, dates and name are still verified.
    var root = X509CertificateLoader.LoadCertificateFromFile("corp-root-ca.pem");
    var handler = new SocketsHttpHandler();
    handler.SslOptions.CertificateChainPolicy = new X509ChainPolicy
    {
        TrustMode = X509ChainTrustMode.CustomRootTrust,
        // a custom policy defaults to online revocation checks; use NoCheck only if the CA publishes no CRL/OCSP
        RevocationMode = X509RevocationMode.NoCheck,
    };
    handler.SslOptions.CertificateChainPolicy.CustomTrustStore.Add(root);
    using var client = new HttpClient(handler);
  4. 4.

    Development: trust the ASP.NET Core dev certificate

    dotnet dev-certs https --trust installs and trusts the localhost development certificate on Windows and macOS (on Linux, trusting it depends on the distribution).

    bash
    # ASP.NET Core development certificate on localhost
    dotnet dev-certs https --trust

Related errors

Browse the full signature error database