System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception.
with the inner AuthenticationException: The remote certificate is invalid because of errors in the certificate chain: UntrustedRoot
means the server's certificate does not chain to a root the client's machine trusts. In WSL, calling
an app that runs on the ASP.NET Core development certificate, the cause is that Ubuntu's certificate
store has never heard of that certificate.
Export the development certificate as PEM, copy it into
/usr/local/share/ca-certificates and run sudo update-ca-certificates. The same
HttpClient call then succeeds, with no change to the code.
The error
Unhandled exception. 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
The client was a console app doing await http.GetStringAsync("https://localhost:15750/")
against an empty web app in the same distribution. curl told the same story in its own words,
curl: (60) SSL certificate problem: self-signed certificate, and Kestrel had warned at
startup that "The ASP.NET Core developer certificate is not trusted."
Why it happens
The development certificate is self-signed: it is its own root. The first dotnet new in
the distribution created it for the current user and printed "Installed an ASP.NET Core HTTPS
development certificate", but creating a certificate is not trusting it. On Linux, HttpClient checked
the chain against the system store, the same one curl reads, and nothing had added the certificate
there, so the chain ended at an unknown root: UntrustedRoot.
Trusting the certificate on Windows does not help either. WSL is a separate Linux system with its own store and its own certificate; the Windows side is covered in the developer certificate on Windows, in WSL and in a container.
The fix
mkdir -p ~/.aspnet/https
dotnet dev-certs https -ep ~/.aspnet/https/aspnetcore.crt --format PEM
sudo cp ~/.aspnet/https/aspnetcore.crt /usr/local/share/ca-certificates/aspnetcore.crt
sudo chmod 644 /usr/local/share/ca-certificates/aspnetcore.crt
sudo update-ca-certificates
Updating certificates in /etc/ssl/certs...
rehash: warning: skipping ca-certificates.crt,it does not contain exactly one certificate or CRL
1 added, 0 removed; done.
The client then printed Hello World!, and so did curl. The first line matters: without
the folder, the export failed with exit code 3 and "Please create the target directory before
exporting." The export is written with mode 600, hence the chmod. Two things did not
change: dotnet dev-certs https --check --trust still said none of the certificates is
trusted (exit code 7), and Kestrel kept its startup warning. Neither verdict reflects the system
store; the clients that read that store, HttpClient and curl, are the ones that changed.
The shortcut you will find everywhere switches validation off:
// Development only: accepts ANY certificate, including a forged one.
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback =
HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
};
using var http = new HttpClient(handler);
It worked here, and that is the danger: the client now talks to any server that answers, an attacker in the middle included. Keep it out of any code path that can reach production. In a Docker container the cause is the same, an image store that does not know the certificate, and so is the fix, run inside the image; this run did not test a container.
How it was reproduced
A throwaway distribution made with wsl --install Ubuntu-24.04 --name fix6-ef --no-launch
(WSL 2.7.10, Ubuntu 24.04.5 LTS), .NET SDK 10.0.113 with runtime 10.0.13 from Ubuntu's own feed
(sudo apt-get install -y dotnet-sdk-10.0), an unchanged dotnet new web app
started with dotnet run --urls https://localhost:15750, a dotnet new console
client, and curl 8.5.0. The distribution name, the port and the user are the test's own, and the
distribution was unregistered afterwards.
Frequently asked
- How do I fix UntrustedRoot from HttpClient on Ubuntu or WSL?
- Add the server's root certificate to the system store. For the ASP.NET Core development certificate: export it with dotnet dev-certs https -ep and --format PEM, copy it to /usr/local/share/ca-certificates, and run sudo update-ca-certificates.
- Is DangerousAcceptAnyServerCertificateValidator safe to use?
- Only on a development machine against your own local server. It accepts every certificate, so in production it removes the protection HTTPS exists for and lets anyone in the middle read and change the traffic.
- Why does dotnet dev-certs https --check --trust still say not trusted after update-ca-certificates?
- In this test it kept saying so, with exit code 7, after the system store had the certificate. HttpClient and curl use the system store, so once update-ca-certificates has added the certificate they work; judge the fix by the client, not by the tool.
If Kestrel cannot find a certificate at all, the error is a different one: unable to configure HTTPS endpoint. More decoded errors in the Fixes category.