Avoiding Connection Failures with Proper HttpClient Singleton Usage
In distributed applications—whether you’re building APIs, workers, or integrations—outbound HTTP calls are mission-critical. But many developers unknowingly misuse the HttpClient class in .NET, leading to no session matched, socket exhaustion, intermittent timeouts, or "connection closed" errors.
Here bellow you can see a sample scenario of such situation which was visible by monitoring the traffic between the client and server applications:
Let’s fix that once and for ever!
💥 Common Mistake: Creating HttpClient per Request
// ⚠️ Not recommended
using var client = new HttpClient();
var response = await client.GetAsync("https://api.example.com/data");
This approach causes:
✅ Recommended: Singleton or HttpClientFactory
Option 1: Reuse a Static HttpClient
public static class HttpClientFactory
{
public static readonly HttpClient Instance = new HttpClient();
}
Option 2: Use IHttpClientFactory (ASP.NET Core)
services.AddHttpClient(); // Or use named/typed clients
Using HttpClientFactory is preferred in modern .NET, especially for resiliency and diagnostics.
🧠 Best Practices When Using HttpClient
Here’s how to avoid common traps and tune your clients for production:
1. Use SocketsHttpHandler for Fine-Grained Control
var handler = new SocketsHttpHandler
{
PooledConnectionLifetime = TimeSpan.FromMinutes(5),
KeepAlivePingPolicy = HttpKeepAlivePingPolicy.Always,
KeepAlivePingDelay = TimeSpan.FromSeconds(30),
KeepAlivePingTimeout = TimeSpan.FromSeconds(15)
};
var client = new HttpClient(handler);
This ensures:
2. Avoid DNS Staleness
Use PooledConnectionLifetime to force DNS refresh periodically.
⚙️ Advanced: Disabling Connection Pooling or Keep-Alive
In rare but valid cases, you might want to disable connection reuse or keep-alive entirely:
❌ Disable Connection Pooling
Recommended by LinkedIn
var handler = new SocketsHttpHandler
{
EnableMultipleHttp2Connections = true,
MaxConnectionsPerServer = int.MaxValue,
PooledConnectionLifetime = TimeSpan.Zero, // ← forces new connection
PooledConnectionIdleTimeout = TimeSpan.Zero
};
This forces a fresh TCP connection for every request, which is useful:
❌ Disable Keep-Alive
var request = new HttpRequestMessage(HttpMethod.Get, "https://api.example.com");
request.Headers.ConnectionClose = true;
Or via handler:
var handler = new SocketsHttpHandler
{
KeepAlivePingPolicy = HttpKeepAlivePingPolicy.Never
};
Disabling keep-alive can be appropriate when:
🔒 Warning: Disabling pooling or keep-alive should be done carefully—it increases overhead and can degrade performance significantly in high-volume environments.
🔍 Real-World Impact
After adopting these best practices, it is visible that:
📌 Summary
Here you can find a summary of the best practices that can be followed to approach every concern we talked about here in this article:
Sample of HttpClientFactory implementation:
// DISCLAIMER: The sample code and configurations presented in this article are provided as-is, without any warranties or guarantees. They are intended for educational and illustrative purposes only. Before applying any of these examples in a production environment, you must evaluate and validate them in the context of your specific use case, architecture, and operational requirements.
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
public static class HttpClientFactory
{
public static HttpClient CreateClient()
{
// Create a new handler for each client to avoid
// pooled connections
var handler = new SocketsHttpHandler
{
AutomaticDecompression = DecompressionMethods.GZip |
DecompressionMethods.Deflate,
// Disable connection pooling to prevent reuse of sockets
EnableMultipleHttp2Connections = false,
// no limit, since each request will use new handler
MaxConnectionsPerServer = int.MaxValue,
PooledConnectionLifetime = TimeSpan.Zero,
PooledConnectionIdleTimeout = TimeSpan.Zero,
KeepAlivePingPolicy = HttpKeepAlivePingPolicy.Never
};
var client = new HttpClient(handler, disposeHandler: true)
{
Timeout = TimeSpan.FromMinutes(2)
};
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
// Instruct server to close the connection after the request
client.DefaultRequestHeaders.ConnectionClose = true;
return client;
}
}
// ✅ Shared HttpClient
private static readonly HttpClient _httpClient = HttpClientFactory.CreateClient();
✨ Final take away
The HttpClient isn’t just a utility—it’s a critical component that, if misused, can undermine your app’s stability. Build good logging and traceability's to be able to pin point connection failures in a proper way.
Disclaimer
The sample code and configurations presented in this article are provided as-is, without any warranties or guarantees. They are intended for educational and illustrative purposes only. Before applying any of these examples in a production environment, you must evaluate and validate them in the context of your specific use case, architecture, and operational requirements.
References
Have you faced the real problem with it? Because, as I understand, HttpClient is designed to be disposed and created per session, not doing this may lead to some other problems, e.g. https://byterot.blogspot.com/2016/07/singleton-httpclient-dns.html . It will be interesting to know, on what scale it may stop working