SSL
HttpClient
Untrusted Certificates
Security
Networking

Allowing Untrusted SSL Certificates with HttpClient

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

In C#, HttpClient rejects HTTPS requests to servers with untrusted SSL certificates (self-signed, expired, or wrong hostname). During development and testing, you may need to bypass this validation using HttpClientHandler.ServerCertificateCustomValidationCallback. This should never be used in production — it disables the protection that prevents man-in-the-middle attacks.

The Error

csharp
1using var client = new HttpClient();
2var response = await client.GetAsync("https://localhost:5001/api/data");
3// System.Net.Http.HttpRequestException:
4// The SSL connection could not be established, see inner exception.
5// Inner: AuthenticationException: The remote certificate is invalid
6// according to the validation procedure.

This happens because the server's certificate is not trusted by the system's certificate store.

Bypassing SSL Validation (Development Only)

csharp
1var handler = new HttpClientHandler
2{
3    ServerCertificateCustomValidationCallback =
4        HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
5};
6
7using var client = new HttpClient(handler);
8var response = await client.GetAsync("https://localhost:5001/api/data");
9Console.WriteLine(response.StatusCode); // OK

DangerousAcceptAnyServerCertificateValidator is a built-in delegate that returns true for all certificates. The name is intentionally alarming.

Custom Validation Logic

Instead of accepting everything, you can validate specific properties:

csharp
1var handler = new HttpClientHandler
2{
3    ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =>
4    {
5        // Accept if no errors
6        if (errors == System.Net.Security.SslPolicyErrors.None)
7            return true;
8
9        // Accept a specific self-signed certificate by thumbprint
10        if (cert?.GetCertHashString() == "A1B2C3D4E5F6...")
11            return true;
12
13        // Accept certificates for a specific domain during development
14        if (message.RequestUri?.Host == "localhost")
15            return true;
16
17        // Reject everything else
18        return false;
19    }
20};
21
22using var client = new HttpClient(handler);

The callback receives four parameters:

  • message: The HTTP request message
  • cert: The server's X509 certificate
  • chain: The certificate chain
  • errors: What SSL policy errors were detected

Using IHttpClientFactory (ASP.NET Core)

In ASP.NET Core, configure named or typed clients through dependency injection:

csharp
1// Program.cs or Startup.cs
2builder.Services.AddHttpClient("DevClient", client =>
3{
4    client.BaseAddress = new Uri("https://localhost:5001");
5})
6.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
7{
8    ServerCertificateCustomValidationCallback =
9        HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
10});
11
12// Usage in a service
13public class MyService
14{
15    private readonly IHttpClientFactory _clientFactory;
16
17    public MyService(IHttpClientFactory clientFactory)
18    {
19        _clientFactory = clientFactory;
20    }
21
22    public async Task<string> GetDataAsync()
23    {
24        var client = _clientFactory.CreateClient("DevClient");
25        var response = await client.GetAsync("/api/data");
26        return await response.Content.ReadAsStringAsync();
27    }
28}

Environment-Conditional Bypass

Only bypass in development, never in production:

csharp
1var handler = new HttpClientHandler();
2
3if (builder.Environment.IsDevelopment())
4{
5    handler.ServerCertificateCustomValidationCallback =
6        HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
7}
8
9using var client = new HttpClient(handler);

Or use a configuration flag:

csharp
1var bypassSsl = builder.Configuration.GetValue<bool>("BypassSslValidation");
2
3builder.Services.AddHttpClient("ApiClient")
4    .ConfigurePrimaryHttpMessageHandler(() =>
5    {
6        var handler = new HttpClientHandler();
7        if (bypassSsl)
8        {
9            handler.ServerCertificateCustomValidationCallback = (_, _, _, _) => true;
10        }
11        return handler;
12    });

The Right Fix: Trust the Certificate

Instead of bypassing validation, add the self-signed certificate to the trusted store:

bash
1# Windows — import to Trusted Root store
2certutil -addstore -f "ROOT" my-dev-cert.crt
3
4# macOS — add to Keychain
5sudo security add-trusted-cert -d -r trustRoot \
6  -k /Library/Keychains/System.keychain my-dev-cert.crt
7
8# Linux — add to system certificates
9sudo cp my-dev-cert.crt /usr/local/share/ca-certificates/
10sudo update-ca-certificates
11
12# ASP.NET Core dev certificate
13dotnet dev-certs https --trust

After trusting the certificate, HttpClient accepts it without any code changes.

.NET Framework (Older Approach)

In .NET Framework (not .NET Core), the global ServicePointManager controls SSL validation:

csharp
1// .NET Framework only — affects ALL HttpClient instances globally
2ServicePointManager.ServerCertificateValidationCallback +=
3    (sender, cert, chain, errors) => true;
4
5// This is even more dangerous because it's process-wide

In .NET Core and .NET 5+, use HttpClientHandler per-client instead.

Common Pitfalls

  • Using bypass in production: Disabling SSL validation exposes all traffic to man-in-the-middle attacks. Attackers can intercept, read, and modify every request and response. Always use proper certificates in production.
  • Global ServicePointManager: In .NET Framework, ServicePointManager.ServerCertificateValidationCallback affects every HTTP connection in the process, including third-party libraries. Prefer per-handler callbacks.
  • Singleton HttpClient with handler: HttpClient should be long-lived (or use IHttpClientFactory), but the handler's SSL bypass stays active for the client's lifetime. Ensure this is intentional.
  • Certificate pinning conflict: If you implement certificate pinning (checking a specific thumbprint), updating the server certificate requires updating the pinned hash in your code. Plan for certificate rotation.
  • Docker/container environments: Containers often lack the host's trusted certificates. Mount the CA certificate into the container or add it during the Docker build rather than bypassing validation.

Summary

  • Use HttpClientHandler.ServerCertificateCustomValidationCallback to bypass SSL validation in development
  • DangerousAcceptAnyServerCertificateValidator accepts all certificates — use only for testing
  • Write custom validation to accept specific certificates by thumbprint or domain
  • Use IHttpClientFactory in ASP.NET Core to configure SSL handling per named client
  • The proper fix is trusting the certificate in the OS store or using dotnet dev-certs https --trust
  • Never bypass SSL validation in production code

Course illustration
Course illustration

All Rights Reserved.