Australian cloud for business software — built and run in Sydney since 2003

NetLeverage
NetLeverage

Outbound HTTP Calls Fail Under Load: Check HttpClient Connection Lifetime

Technical article
Performance

When outbound HTTP calls fail under load, inspect how the application owns its clients, handlers and connections. Creating and disposing a new owned handler for every request can cause connection churn and port pressure. Keeping connections indefinitely can delay adoption of changed DNS answers.1

The correction is a deliberate lifetime strategy supported by measurements. Microsoft documents two common approaches: long-lived clients with a suitable pooled-connection lifetime, or short-lived clients created by IHttpClientFactory, which manages reusable handlers.1, 2

This article covers modern .NET application behaviour. Network routing, firewall changes and operating-system port-range tuning are outside its scope.

Separate the three lifetimes

An HttpClient holds request settings and uses an underlying handler. The handler manages connection pooling. Connections have their own reuse and lifetime behaviour. Confusing these objects leads to misleading rules such as “always dispose the client” or “never create another client”.1

Disposing a manually created client that owns its handler also disposes that handler and its pool. Disposing a factory-created client does not have the same effect because the factory manages the handler.1, 2

Likewise, creating a new client object through the factory does not necessarily create a new physical connection. The factory may reuse an existing handler and its pool. Client-object counts alone are therefore not a reliable measure of connection churn.

Inventory how clients are constructed, which handlers they use, who disposes each object and which dependency-injection lifetime retains them. Follow that ownership through the actual outgoing request path.

Establish the failure before changing lifetimes

Record the exact exception, inner exception, destination, request duration, runtime version and application build. Distinguish connection establishment failures from HTTP error responses and requests waiting for application capacity.

Correlate failures with request concurrency, connection creation and closure, and the timing of deployment or destination changes. Use bounded, sanitised diagnostics. Headers, paths and query strings can contain customer information, so collection must be deliberate.

A high request rate does not itself prove port exhaustion. A DNS lookup returning a new address does not prove that an existing application connection has moved to it. The evidence must describe the connection behaviour of the affected process.

If the application lifetime pattern appears sound, leave other causes open. A remote service error, network fault or slow dependency cannot be ruled out merely because HttpClient appears in the stack.

Understand connection churn

Microsoft explains that unnecessary handler creation produces new connection pools and additional connection setup. TCP ports are not immediately available for reuse after connection closure, so sufficiently high churn can exhaust available ports.1

The relevant application defect is often ownership at the wrong scope: request-level code creates and destroys the underlying pool repeatedly. The fix should preserve appropriate reuse while maintaining isolation where it is required.

Multiple clients can be legitimate for different proxies, settings or cookie boundaries. The objective is not to force every outbound call in a process through one universal instance. It is to avoid accidental pool destruction and uncontrolled proliferation.

Measure both connection creation and request outcomes after a correction. A lower client-object count is not enough if handlers still churn or requests remain stalled.

Understand DNS refresh

HttpClient resolves DNS when it creates a connection; it does not track the DNS server's TTL to refresh an already established connection. Microsoft's guidance uses PooledConnectionLifetime to limit connection reuse and create opportunities for fresh resolution.1

This setting governs a connection's age. It is not a DNS TTL setting or a guarantee that all traffic switches addresses at an exact instant. The documented behaviour closes an expired connection after its associated request completes, with new connections created as needed.1

Choose a lifetime based on the environment's expected changes and connection costs. Microsoft's example interval is explicitly illustrative. Copying it into every application is not a measured configuration decision.

Verification should include an approved destination-change scenario if that behaviour matters to the service. Observe the application's new connections and request outcomes, not only a separate resolver test.

Use the factory as designed

IHttpClientFactory caches handlers and supplies short-lived clients. HandlerLifetime controls how long a handler can be reused by the factory; it is distinct from the lifetime of an individual pooled connection.2

The documented default handler lifetime is two minutes. Treat this as a default to inspect, not a universal DNS-refresh deadline or a replacement for the application's actual lifetime settings.2

A client remains tied to the handler it received. Retaining that client indefinitely can prevent it from benefiting from newly created handlers. Microsoft's guidance therefore emphasises obtaining factory-created clients in a timely way.2

Typed clients can make configuration and dependency use easier to organise, but retaining a typed client in a singleton can inadvertently extend its lifetime. Review the consuming service's lifetime, not just the typed client's registration.2

The factory and SocketsHttpHandler can also be configured together. If both handler rotation and pooled-connection lifetime are involved, document which mechanism is responsible for connection turnover. Do not change both blindly and then attribute an improvement to an unknown cause.

Cookies and identity need explicit boundaries

Microsoft warns that factory-pooled handlers can share CookieContainer instances and that handler recycling loses cookies stored in those containers. The guidance recommends avoiding the factory pattern when the application requires cookies unless an appropriate alternative design is established.1, 2

Do not solve connection churn by accidentally sharing authentication state between unrelated users or integrations. List which settings are safe to share and which are specific to one request or identity.

This article does not supply a universal singleton code snippet because handler ownership, cookies, proxies and authentication can change the correct design. A short snippet that omits those conditions would encourage a misleading drop-in fix.

Lifetime is only one part of concurrency

Reusable connections do not give an application unlimited downstream capacity. Review the amount of simultaneous work, timeouts, cancellation and retry design alongside the lifetime pattern.

As an editorial caution, do not infer one connection per outstanding request without checking the negotiated protocol and handler behaviour. Connection and request counts measure different things; the investigation must use the protocol-specific evidence available in the actual deployment.

Retries also need application semantics. Repeating an operation after an uncertain response may duplicate business work. A lifetime change should not be bundled with an aggressive retry policy merely to make transient errors disappear from logs.

No operating-system port tuning is prescribed here. Such a change would require separate evidence and ownership; it would not establish that an application-level pool-lifetime defect had been corrected.

Verify the ownership change under relevant conditions

Compare a stable workload before and after the change: request latency, failures, connection creation, resource use and response to expected destination changes. Include the client's actual lifetime and handler configuration in the record.

Check required identity and cookie isolation as part of functional acceptance. A throughput improvement that changes session behaviour is not a successful correction.

If the available evidence shows only fewer failures, say so. Do not claim the exact mechanism is proven unless the connection and lifetime observations support it. The strongest conclusion connects the original ownership pattern to the measured failure and the corrected behaviour.

Learning notes

  • Client, handler and connection lifetimes are different.
  • Factory-created clients can be short-lived while their handlers are reused.
  • DNS changes are picked up when new connections resolve names.
  • Connection reuse must preserve cookie, proxy and identity boundaries.

Acceptance checklist

  • [ ] Client, handler and connection ownership/lifetimes are recorded separately.
  • [ ] The chosen supported reuse strategy preserves identity, proxy and cookie boundaries.
  • [ ] Approved comparison covers connection churn, latency, failures and expected destination changes.
  • [ ] The conclusion states observed evidence without treating a default lifetime as a DNS guarantee.

References

  1. HttpClient guidelines for .NET - .NET | Microsoft Learn. Accessed 2026-11-01.
  2. Use the IHttpClientFactory - .NET | Microsoft Learn. Accessed 2026-11-01.

Related articles