Skip to content

Build1 publisher2 min readPublished

Creating a new HttpClient per request spends an ephemeral port the kernel cannot immediately reuse

Najeeb Ullah traces intermittent SocketExceptions in an ASP.NET Core payments service to connection lifecycle, where each per-request client gets its own pool and each close parks a local port in TIME_WAIT while CPU, memory and database graphs stay flat.

The Engineer · Build desk

Illustration accompanying Creating a new HttpClient per request spends an ephemeral port the kernel cannot immediately reuse

What happened

  • A dev.to post by Najeeb Ullah walks through an ASP.NET Core payments service that creates and disposes an HttpClient inside every request handler before posting to an external payment API.
  • As production traffic increases the service starts timing out on those outbound calls and throwing SocketException, and a restart clears the problem for a while before it returns.
  • The post's explanation is that each HttpClient owns or uses a connection pool through its underlying handler, so per-request clients mean per-request connection pools and fresh connection establishment.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • constraint The ceiling is the local ephemeral port range, which the post says varies with operating system and configuration, so identical code fails at different traffic levels on different hosts and a staging box proves little.
  • decision Healthy CPU, memory and database readings push an on-call engineer toward adding capacity, when the resource being consumed is one those graphs do not measure.
  • cost Remediation is a client lifetime change at every call site that news up a client, not new hardware, and it lands on whoever owns that code.
  • capability A team that learns the connection lifecycle can recognise the same exhaustion pattern in stacks where the class name is different.

Follow one request down. `using var client = new HttpClient()` hands you a client whose underlying handler owns or uses a connection pool, and disposing it at the end of the method takes that pool with it [5]. The pool goes out of scope with the client, which is exactly what `using` is for. So the next request establishes its own connection, and every outbound TCP connection needs a local source port drawn from the operating system's ephemeral range [7].

Closing the connection does not hand the port straight back. After certain close scenarios the endpoint can stay in TIME_WAIT for a period, because TCP has to stop a delayed packet from an earlier connection being read as part of a newer one [8]. Najeeb Ullah wrote that TIME_WAIT "is part of TCP's correctness model" [9]. The failure is a rate mismatch: the application creates connections faster than the system recycles the resources behind them [12].

The post's illustration is 500 new outbound connections per second [11]. Sustain that for a minute and the process has created 30,000 connection endpoints [13]. Whether 30,000 is a problem depends on two quantities. The post says the ephemeral port range varies by operating system and configuration [7], and it gives TIME_WAIT only as "a period of time" [8]. For the 500 figure to mean anything on your service, you need the same one-connection-per-request shape and a comparable request rate. It is a supposition in the post, not a measurement of a running system.

This is why the graphs stay flat. CPU, memory and database metrics read normal while the process is already running out of a critical resource [3][14], and the resource that accumulates sits outside the working set. In my view the restart is the most useful signal available. Ullah wrote that it "tells you something has accumulated inside the process or its operating-system networking resources" [4].

The remediation is old. Microsoft recommends reusing HttpClient instances or using IHttpClientFactory instead of creating and disposing one per request, according to the post, and current .NET guidance cited there says TCP ports are not released immediately after closure and that high request rates can exhaust available ports [6][10]. Ullah's complaint is that most write-ups stop at the instruction, saying "Don't create HttpClient repeatedly. Use IHttpClientFactory." without explaining why [15]. He argues the why is the part that transfers to other stacks: understand it and you recognise the same failure pattern when the technology changes [17].

What to watch

  • Whether the post follows its creation-rate equation with a measured port count from a real service.
  • Whether the .NET guidance cited on ports not being released immediately is updated with a concrete range or recycle interval.
  • Whether teams reporting this failure resolve it as an IHttpClientFactory lifetime change or as host-level ephemeral port tuning.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories