A static HttpClient (or a handler you never rotate) will keep TCP connections open for the life of w3wp.exe. On IIS that process often runs for days. When a dependency moves IP—blue/green cutover, CDN origin swap, vendor failover, or a simple A-record change—your app keeps dialing the old address until the app pool recycles. Callers see intermittent timeouts and connection refused errors that vanish after you bounce the pool, which is the wrong fix.

The durable fix is to bound how long a pooled connection may live so DNS is re-resolved on a schedule you control. .NET’s SocketsHttpHandler exposes that knob; IHttpClientFactory applies a sensible default when you use it correctly. Shared and VPS Windows hosts amplify the issue because pools are long-lived by design and you rarely want aggressive idle shutdowns just to clear sockets.

#Why IIS app pools make stale DNS hurt

Kestrel-behind-ANCM (in-process) and out-of-process hosting both leave managed code running inside a durable worker. Outbound HttpClient pools are process-wide. IIS may recycle on schedule, memory limit, or configuration change, but none of those events track your dependency’s DNS TTL. A five-minute DNS TTL with a twelve-hour pool lifetime means hours of bad routes after an upstream move.

Classic failure pattern on hosted ASP.NET Core: payment or email API migrates, your site’s webhook and back-office jobs keep the old VIP, support sees “works after recycle.” Application event logs show HttpRequestException or TaskCanceledException on outbound calls while inbound IIS health looks fine. Failed Request Tracing will not help—you are failing on the client side of the worker, not on the incoming request pipeline.

#Use IHttpClientFactory first

Prefer typed or named clients from IHttpClientFactory. The factory rotates handlers on HandlerLifetime (two minutes by default in current .NET), which caps how long a physical handler—and its DNS resolution—survives. Do not wrap a singleton HttpClient around a bare new HttpClientHandler() and share it for the app lifetime; that reintroduces the stale-DNS trap the factory was built to avoid.

csharp
// Program.cs — .NET 10 / ASP.NET Core on IIS
builder.Services.AddHttpClient<OrdersApiClient>(client =>
{
    client.BaseAddress = new Uri("https://api.partner.example/");
    client.Timeout = TimeSpan.FromSeconds(30);
})
// Optional: tighten or loosen handler rotation (default is 2 minutes)
.SetHandlerLifetime(TimeSpan.FromMinutes(5));

Inject OrdersApiClient (or IHttpClientFactory) where you need outbound calls. Avoid static readonly HttpClient fields in libraries that run under IIS. If a third-party SDK demands an HttpClient instance, create it from the factory per the SDK’s guidance or pass a factory-created client rather than a process-wide singleton you constructed at startup.

#When you own SocketsHttpHandler

Custom primary handlers, mutual TLS, or special proxy rules mean you configure SocketsHttpHandler yourself. Set PooledConnectionLifetime so connections are torn down and re-resolved even if traffic is steady. PooledConnectionIdleTimeout reclaims quiet sockets sooner. Leaving PooledConnectionLifetime at infinite (the handler’s historical default when you new it up yourself) is what pins DNS for the life of the pool.

csharp
builder.Services.AddHttpClient("partner")
    .ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler
    {
        // Re-resolve DNS at least this often, even under constant load
        PooledConnectionLifetime = TimeSpan.FromMinutes(5),
        // Drop idle sockets faster than the lifetime cap
        PooledConnectionIdleTimeout = TimeSpan.FromMinutes(2),
        AutomaticDecompression = DecompressionMethods.All
    });

Five minutes is a practical starting point for most vendor APIs: short enough to ride out DNS cutovers without waiting for an IIS recycle, long enough that you still benefit from connection reuse. Match it to how aggressively your dependency moves traffic. Do not set multi-hour lifetimes “for performance” on IIS—the cost of occasional reconnects is far lower than hours of hard-to-reproduce outages.

#Host-floor checks before you ship

  • Search the solution for new HttpClient( and static HttpClient fields; route them through the factory.
  • Confirm custom ConfigurePrimaryHttpMessageHandler paths set PooledConnectionLifetime (not only timeouts).
  • Keep app pool idle timeouts and recycle schedules for memory/stability—not as your DNS refresh strategy.
  • Log dependency base addresses and failure rates so a vendor DNS cutover shows up as outbound errors, not vague “site slowness.”
  • On Windows Server, remember outbound calls use the worker identity’s network path; firewall and proxy rules still apply after DNS is fixed.

If you still need a one-off validation after a vendor announces an IP change, recycle a staging pool first and watch outbound success metrics. Production should not rely on manual recycles. Web Deploy and CI releases that only bounce the pool “to clear sockets” are papering over missing handler lifetimes.

#Practical takeaway

Treat outbound connection lifetime as part of your IIS hosting checklist next to app pool recycle settings and SQL connection pooling. Use IHttpClientFactory for normal ASP.NET Core 10 code, set PooledConnectionLifetime whenever you construct SocketsHttpHandler yourself, and keep that lifetime in the low-minute range. After deploy, verify there are no process-wide HttpClient singletons left in shared libraries. Doing that once removes a whole class of “fixed by recycle” incidents on long-lived Windows workers—whether you run the app on a dedicated box or on a Windows shared host where pool uptime is measured in days.