<!-- https://pkizerotohero.com/errors/dotnet-remote-certificate-invalid/ | Markdown mirror of the HTML page | PKI Zero to Hero, by Duyutech BV -->

> Fix .NET and PowerShell's The remote certificate is invalid according to the validation procedure: read the reason, then fix trust, chain, dates or name.

Source: https://pkizerotohero.com/errors/dotnet-remote-certificate-invalid/ · Last reviewed: 2026-10-20

- .NET
- Exact text checked with the real tool

# The remote certificate is invalid according to the validation procedure

Also seen in: PowerShell, Invoke-WebRequest, Invoke-RestMethod, HttpClient, SslStream, SQL Server clients

**In one sentence:**

.NET rejected the server's certificate; the reason after the colon (UntrustedRoot, PartialChain, NotTimeValid or RemoteCertificateNameMismatch) tells you whether trust, the chain, the dates or the name is wrong.

[Check yours: Certificate decoder](https://pkizerotohero.com/tools/decoder/)

## Where you see it

In .NET applications that use `HttpClient` or `SslStream`, and in PowerShell’s `Invoke-WebRequest` and `Invoke-RestMethod`. The exception is an `AuthenticationException`, often wrapped in another one.

Windows PowerShell 5.1 (.NET Framework) shows the outer message; the title text is in the inner exception:

```text
Invoke-WebRequest : The underlying connection was closed: Could not establish trust relationship for the SSL/TLS
secure channel.
At line:1 char:1
+ Invoke-WebRequest -UseBasicParsing https://api.example.com/
```

```text
PS> $Error[0].Exception.InnerException.Message
The remote certificate is invalid according to the validation procedure.
```

Since .NET 5, the message adds the reason after a colon ([`System.Net.Security` strings](https://github.com/dotnet/runtime/blob/main/src/libraries/System.Net.Security/src/Resources/Strings.resx)). These are real messages from .NET 10:

```text
System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception.
 ---> System.Security.Authentication.AuthenticationException: The remote certificate is invalid because of errors in the certificate chain: UntrustedRoot
```

```text
The remote certificate is invalid because of errors in the certificate chain: PartialChain
The remote certificate is invalid because of errors in the certificate chain: NotTimeValid, PartialChain
The remote certificate is invalid according to the validation procedure: RemoteCertificateNameMismatch, RemoteCertificateChainErrors
```

## What it means

.NET checks three things: that the [certificate chain](https://pkizerotohero.com/handbook/glossary/#certificate-chain) leads to a trusted root, that no certificate is expired, and that the server’s name is in the certificate. The words after the colon are values of [`SslPolicyErrors`](https://learn.microsoft.com/dotnet/api/system.net.security.sslpolicyerrors) and [`X509ChainStatusFlags`](https://learn.microsoft.com/dotnet/api/system.security.cryptography.x509certificates.x509chainstatusflags) and name the failed check.

## Likely causes

### UntrustedRoot: the root is not trusted

The chain is complete, but its root is not in the machine’s [trust store](https://pkizerotohero.com/handbook/glossary/#trust-store): a company CA, a [TLS-inspection](https://pkizerotohero.com/handbook/glossary/#tls-inspection) proxy, or a self-signed development certificate. On Windows, inspect the chain of a saved server certificate:

```powershell
$cert  = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new("C:\temp\server.cer")
$chain = [System.Security.Cryptography.X509Certificates.X509Chain]::new()
$chain.ChainPolicy.RevocationMode = "NoCheck"
$chain.Build($cert)
$chain.ChainStatus | Format-Table Status, StatusInformation
$chain.ChainElements | ForEach-Object { $_.Certificate.Subject }
```

**Fix:** get the root from your PKI team or IT and install it for the machine:

```powershell
Import-Certificate -FilePath C:\certs\corp-root.cer -CertStoreLocation Cert:\LocalMachine\Root
```

On Linux, .NET uses the system OpenSSL store, which `SSL_CERT_FILE` and `SSL_CERT_DIR` can override ([cross-platform cryptography in .NET](https://learn.microsoft.com/dotnet/standard/security/cross-platform-cryptography)): copy the root to `/usr/local/share/ca-certificates/corp-root.crt` and run `update-ca-certificates` (Debian, Ubuntu and the official .NET container images), or use `/etc/pki/ca-trust/source/anchors/` and `update-ca-trust extract` on RHEL.

If only one application should trust a private root, use a custom trust store in code instead of switching checks off (this example needs .NET 9 or later):

```csharp
var policy = new X509ChainPolicy { TrustMode = X509ChainTrustMode.CustomRootTrust };
policy.CustomTrustStore.Add(X509CertificateLoader.LoadCertificateFromFile("corp-root.pem"));
var handler = new SocketsHttpHandler();
handler.SslOptions.CertificateChainPolicy = policy;
var client = new HttpClient(handler);
```

A new `X509ChainPolicy` checks revocation online by default. If the private CA publishes no reachable CRL or OCSP service, you will see `RevocationStatusUnknown`; fix the CA’s revocation service rather than disabling the check.

### PartialChain: the chain could not be built

.NET could not build a path to any root. Either the server does not send its [intermediate certificate](https://pkizerotohero.com/handbook/glossary/#intermediate-certificate) and .NET could not download it (.NET normally fetches it from the certificate’s AIA address, see [`DisableCertificateDownloads`](https://learn.microsoft.com/dotnet/api/system.security.cryptography.x509certificates.x509chainpolicy.disablecertificatedownloads), which fails on servers without outbound internet access), or the root is unknown. Check what the server sends:

```bash
openssl s_client -connect api.example.com:443 -servername api.example.com -showcerts </dev/null 2>/dev/null | grep -E '^ [0-9]+ s:|^   i:'
```

**Fix:** configure the server with its full chain (certificate first, then intermediates); on IIS, install the intermediate in the computer’s “Intermediate Certification Authorities” store. If the chain is complete, install the root as above.

### NotTimeValid: a certificate expired or is not valid yet

Check the dates: `$cert.NotBefore` and `$cert.NotAfter` in PowerShell. Renew and redeploy an expired certificate; if the dates look right, check the client’s clock with `w32tm /query /status`.

### RemoteCertificateNameMismatch: wrong name

The URL’s hostname is not in the certificate’s [Subject Alternative Name](https://pkizerotohero.com/handbook/glossary/#subject-alternative-name) list. Connect with a listed name or reissue the certificate with every name clients use. Connecting by IP address needs an IP entry in the list.

Warning

Do not return true from the validation callback

`ServerCertificateCustomValidationCallback = (...) => true`, `HttpClientHandler.DangerousAcceptAnyServerCertificateValidator`, `ServicePointManager.ServerCertificateValidationCallback = { $true }` and PowerShell 7’s `-SkipCertificateCheck` accept any certificate from anyone. They often end up in production. Trust the right root, fix the chain, or use `CustomRootTrust` for one private root.

## Check yours

*Interactive tool: Certificate decoder (runs in the browser at [/tools/decoder/](https://pkizerotohero.com/tools/decoder/))*

Paste the server’s certificate (export it from the browser or from `openssl s_client`). The decoder shows the issuer, the validity dates and the SAN list, which covers each reason above.

## Learn why

- [Who do we trust? CAs, trust stores and self-signed certificates](https://pkizerotohero.com/handbook/trust-stores/)
- [Certificate chains and path validation](https://pkizerotohero.com/handbook/certificate-chains/)
- [Names and jobs: SANs, wildcards, key usage and the CA flag](https://pkizerotohero.com/handbook/names-and-key-usage/)
- See also: Windows PowerShell 5.1’s [Could not establish trust relationship for the SSL/TLS secure channel](https://pkizerotohero.com/errors/powershell-could-not-establish-trust/).

**Early edition.** Checked against the standards; an independent expert review is in progress. Found a mistake? [Tell us](https://pkizerotohero.com/contact/?topic=content-error&page=/errors/dotnet-remote-certificate-invalid/).

Last reviewed 20 October 2026Edition 0.9

## Learn why it happens

- [Chapter 12: Who do we trust? CAs, trust stores and self-signed certificates](https://pkizerotohero.com/handbook/trust-stores/)
- [Chapter 13: Certificate chains and path validation](https://pkizerotohero.com/handbook/certificate-chains/)
- [Chapter 9: Names and jobs: SANs, wildcards, key usage and the CA flag](https://pkizerotohero.com/handbook/names-and-key-usage/)

## Fix it step by step

- [How to trust a private root CA on Windows](https://pkizerotohero.com/guides/add-root-ca-windows/)
- [How to install a certificate in IIS](https://pkizerotohero.com/guides/install-certificate-iis/)
- [How to automate certificates on Windows with win-acme](https://pkizerotohero.com/guides/win-acme-windows/)

## Everything on this topic

- [Chains and trust stores](https://pkizerotohero.com/topics/chains-and-trust/)
