How DNS Resolution Actually Affects a Running .NET App

DNS problems are easy to investigate once you follow the path from an HTTP request to a socket. The useful question is not just "What address does this name resolve to?" It is "Did this request need a new connection, and if it did, what happened at each stage of establishing one?". HttpClient sends requests through a handler with a connection pool. If a suitable connection is already available, the request can use it without resolving the hostname again. When a new connection is needed, the handler resolves the hostname as part of connecting. According to Microsoft's HttpClient guidance, it does not track the DNS server's time-to-live value and refresh a live connection when that value expires. This explains a common sequence after an endpoint change. DNS now points to a new address, but an existing connection can continue carrying requests to the old endpoint. The application and the command line lookup can both be behaving as designed while observing different parts of the system.
The opposite pattern also occurs. Established connections keep succeeding, but requests that need new connections begin to fail. That might expose a resolver failure, a bad new address, a blocked route, a refused connection or a TLS problem. Labelling all of these "DNS" because the failure followed a DNS change sends the investigation in the wrong direction. There is another layer below the application. A .NET DNS lookup calls into the name resolution facilities available on the host. What happens beneath that call depends on the operating system and environment. A container can have different resolver settings from the host, and a laptop can use an entirely different network path. A .NET DNS activity measures the managed lookup call; it does not prove that a physical DNS query went over the network for that particular call.
Find the failing stage
.NET exposes separate networking metrics and traces for name resolution, connection setup, socket connection and TLS. That gives you a way to distinguish several incidents that look alike in an application log. If dns.lookup.duration rises or lookups fail with host_not_found or try_again, investigate name resolution from the application environment. If lookups succeed but socket connections fail, inspect the returned addresses, destination ports, routes and upstream readiness. If the socket connects and the TLS handshake fails, look at certificates, server name indication and protocol negotiation. If the request waits for a connection from the pool, the delay happened before any fresh lookup for that request.
The absence of a DNS activity on a successful request is normal when the request reuses a connection. The absence of one on a failing request needs context: the failure may have occurred on an existing connection, before a connection attempt, or in instrumentation you have not enabled. Do not interpret one missing span as a complete diagnosis.
.NET's built in connection setup activity includes the peer IP address when setup succeeds. The DNS lookup activity can include the returned addresses. In .NET 9 and later these detailed activities use Experimental.System.Net.* sources. The connection setup activity is a separate trace root; a later HTTP request can link to it rather than placing it in a simple parent child span tree. That detail is easy to miss if your trace viewer only displays child spans.
Capture a small reproduction
For a local investigation, you can subscribe to the built-in activities and send several requests through the same client. The following .NET 10 console example prints DNS, connection setup, socket and TLS activities. Supply a test URL on the command line; use an endpoint you are authorised to call.
using System.Diagnostics;
if (args.Length != 1 ||
!Uri.TryCreate(args[0], UriKind.Absolute, out var target) ||
target.Scheme is not ("http" or "https"))
{
Console.Error.WriteLine("Usage: dotnet run -- https://example.org/");
return;
}
using var listener = new ActivityListener
{
ShouldListenTo = source =>
source.Name is "Experimental.System.Net.NameResolution" or
"Experimental.System.Net.Http.Connections" or
"Experimental.System.Net.Sockets" or
"Experimental.System.Net.Security",
Sample = (ref ActivityCreationOptions<ActivityContext> _) =>
ActivitySamplingResult.AllDataAndRecorded,
SampleUsingParentId = (ref ActivityCreationOptions<string> _) =>
ActivitySamplingResult.AllDataAndRecorded,
ActivityStopped = activity =>
{
var tags = string.Join(", ", activity.TagObjects.Select(
tag => $"{tag.Key}={tag.Value}"));
Console.WriteLine(
$"{activity.DisplayName}: {activity.Duration.TotalMilliseconds:N1} ms " +
$"status={activity.Status} {tags}");
}
};
ActivitySource.AddActivityListener(listener);
using var client = new HttpClient();
for (var attempt = 1; attempt <= 5; attempt++)
{
try
{
using var response = await client.GetAsync(
target,
HttpCompletionOption.ResponseHeadersRead);
Console.WriteLine($"Request {attempt}: {(int)response.StatusCode}");
}
catch (HttpRequestException exception)
{
Console.WriteLine($"Request {attempt}: {exception}");
}
await Task.Delay(TimeSpan.FromSeconds(2));
}
On an ordinary run you might see connection and DNS activities for the first request and none for subsequent requests. That does not indicate that telemetry stopped working. It indicates that you need to consider connection reuse. A server may close a connection, or the client may need another one for reasons such as concurrency, so do not expect the same sequence on every run. Do not make a production logging feature out of this exact snippet. It prints full exception text and potentially sensitive hostnames or addresses. Detailed networking activities are useful for a bounded investigation, but they can be noisy at high volume. Capture them selectively and apply your normal telemetry controls.
Check from the right environment
When an incident happens, collect evidence from the application instance that failed. Record its timestamp, deployment version, target hostname, request error, and whether other instances are healthy. Compare those observations with the DNS answers and connection attempts seen in that environment. A command line lookup from inside the same container or host can be helpful, but it remains a separate lookup made at a separate time. It will not tell you which IP address an existing pooled connection uses. Likewise, calling Dns.GetHostAddressesAsync in your application to log a fresh result does not reveal the address of a connection that has already been established. The distinction becomes important when a rollout is uneven. One instance may still have working old connections. Another may create a new connection to a destination that is not ready. A third may be unable to resolve the name. Aggregated HTTP error rates can make those three cases look like one intermittent failure. Split the observations by instance and by failure stage. Check the dependency's deployment timeline as well. A DNS update, endpoint readiness change, certificate rotation or network policy change can happen close together. Temporal correlation gives you a place to start; the socket and TLS evidence tells you which part actually failed.
Change the setting that matches the evidence
If the investigation shows that requests keep using old connections after a DNS change, a connection lifetime is one way to bound how long those connections are eligible for reuse. A long lived client can configure SocketsHttpHandler.PooledConnectionLifetime. The interval should reflect how frequently the destination changes and the cost of establishing new connections. It is a connection policy, not a promise that every request will resolve DNS at that interval. With IHttpClientFactory, handler rotation is another route, provided you obtain fresh clients over time. A factory created client captured by a singleton can keep its original handler beyond the configured handler lifetime. Repeatedly asking the factory for a client does not help a service that stored one client at startup.
Neither setting fixes an unavailable DNS resolver, an address that refuses connections or a TLS handshake failure. Shortening connection lifetimes in those cases may increase the number of failing connection attempts. Verify the stage first, make one targeted change, and watch name resolution duration, connection setup failures, connection counts and end to end request latency afterwards. The most useful outcome of a DNS investigation is often a more precise incident description: "This instance continued using a connection to the old address," "New lookups failed inside these containers," or "DNS returned the expected address, but the new endpoint refused connections." Each description leads to a different fix. A fresh nslookup alone cannot give you that answer.





