An end-to-end tour of one HTTPS request: DNS, IP routing, ports, TCP handshake, TLS, HTTP, load balancers and proxies, connection reuse, and where time goes.
api.example.com to an IP address (OS cache, then a resolver).api.example.com (TLS 1.3: one more round trip).GET /orders/42 with headers; often a load balancer or reverse proxy terminates TLS and forwards to an app instance.HttpClient keeps alive and reuses for the next request, skipping steps 1–4.On a cold connection, DNS + TCP + TLS can cost more than the request itself, which is why connection reuse matters.
.NET HttpClient api.example.com
│
1 │── DNS query: api.example.com A/AAAA? ──► resolver ──► answer 203.0.113.10 (TTL 60)
│
2 │── SYN ────────────────────────────────────────────► load balancer :443
│◄── SYN-ACK ────────────────────────────────────────── (1 RTT)
│── ACK ────────────────────────────────────────────►
│
3 │── TLS ClientHello (SNI=api.example.com, ALPN=h2) ──────────────►
│◄── ServerHello + certificate + Finished ───────────────────── (TLS 1.3: 1 RTT)
│ (client validates chain + hostname)
│
4 │── HTTP/2 HEADERS: GET /orders/42, Authorization, Accept ────────► LB ──► app pod :8080
│◄── HEADERS 200 + DATA {json} ────────────────────────────────── LB ◄── app
│
5 │ connection stays open in HttpClient's pool for the next request
| Step | Cold connection | Warm (reused) connection |
|---|---|---|
| DNS | 1–50 ms (0 if cached) | 0 |
| TCP handshake | 15 ms | 0 |
| TLS 1.3 handshake | 15 ms (+ CPU) | 0 |
| Request + server work + response | 15 ms + server time | 15 ms + server time |
A cold call can be three or four times slower than a warm one before the server does any work. Across regions (Sydney to US West, ~150 ms RTT) a cold call costs 450 ms+ in handshakes alone.
A Blazor Server app called an internal API through a new HttpClient per request. Application Insights showed 120 ms dependency calls for a 5 ms endpoint. Splitting the timing with HttpClient diagnostics events showed DNS + connect + TLS taking 100 ms each time because nothing was reused. Moving to IHttpClientFactory brought calls down to 15 ms.
A classic broad question that shows whether the candidate has a mental model of the network stack and where latency and failures come from.
Green flags
Red flags
Layer (TCP/IP model) What it does here .NET / tooling
───────────────────────────────────────────────────────────────────────────
Application HTTP request/response, DNS HttpClient, Dns.GetHostAddressesAsync
Security TLS encryption, certificate check SslStream (inside SocketsHttpHandler)
Transport TCP (or UDP for HTTP/3), ports Socket
Internet IP addresses, routing IPAddress, ping, traceroute
Link Ethernet/Wi-Fi frames, MAC (OS and NIC)
The OS resolver checks its cache and hosts file, then asks the configured DNS server, which may recurse through root, .com and example.com name servers. The answer has a TTL. .NET asks the OS (getaddrinfo); it does not keep its own DNS cache.
Your machine sends packets to its default gateway. Home and cloud networks usually NAT private addresses (e.g. 10.x) to a public IP. Each router hop forwards based on the destination IP. traceroute / tracert shows the hops.
The client picks an ephemeral source port (e.g. 51544) and connects to 443. The connection is identified by the 4-tuple (client IP, client port, server IP, 443). TCP guarantees ordered, reliable delivery and controls the sending rate.
The client sends the hostname in SNI so a load balancer hosting many sites can pick the right certificate, and lists protocols in ALPN (h2, http/1.1). It validates the certificate chain to a trusted root and checks the name matches.
Often the request passes through a CDN or WAF, then a load balancer or ingress controller that terminates TLS and forwards plain HTTP (or re-encrypted HTTPS) to a pod. Headers like X-Forwarded-For and X-Forwarded-Proto carry the original client details.
SocketsHttpHandler publishes metrics (System.Net.Http, System.Net.NameResolution, System.Net.Sockets) including DNS lookup duration, connection setup time and open connections. View with:
dotnet-counters monitor -n MyApp --counters System.Net.Http,System.Net.NameResolution
OpenTelemetry's HTTP client instrumentation also records these as spans and metrics.
curl -w "dns:%{time_namelookup} connect:%{time_connect} tls:%{time_appconnect} total:%{time_total}\n" -o /dev/null -s https://example.com--http2 and compare.dig example.com and traceroute example.com (or tracert).HttpClient and print Stopwatch times; the first call is slowest.// Measure where time goes for one request (cold vs warm) using HttpClient events
using System.Diagnostics;
var handler = new SocketsHttpHandler
{
PooledConnectionLifetime = TimeSpan.FromMinutes(2),
ConnectCallback = async (ctx, ct) =>
{
var sw = Stopwatch.StartNew();
var addresses = await Dns.GetHostAddressesAsync(ctx.DnsEndPoint.Host, ct);
Console.WriteLine($"DNS {ctx.DnsEndPoint.Host} → {addresses[0]} in {sw.ElapsedMilliseconds} ms");
sw.Restart();
var socket = new Socket(SocketType.Stream, ProtocolType.Tcp) { NoDelay = true };
await socket.ConnectAsync(addresses, ctx.DnsEndPoint.Port, ct);
Console.WriteLine($"TCP connect in {sw.ElapsedMilliseconds} ms");
return new NetworkStream(socket, ownsSocket: true); // TLS happens after this
}
};
using var client = new HttpClient(handler);
for (int i = 0; i < 3; i++)
{
var sw = Stopwatch.StartNew();
using var response = await client.GetAsync("https://example.com/");
Console.WriteLine($"Request {i + 1}: {(int)response.StatusCode} HTTP/{response.Version} in {sw.ElapsedMilliseconds} ms");
}
// Request 1 prints DNS + TCP lines and is slowest; requests 2 and 3 reuse the connection.
# Same breakdown from the command line
curl -so /dev/null -w "dns %{time_namelookup}s tcp %{time_connect}s tls %{time_appconnect}s ttfb %{time_starttransfer}s total %{time_total}s\n" https://example.com
Mistake: Describing only "the client sends a request and the server responds". Why it is wrong: It skips DNS, TCP and TLS, which are where most cold-call latency and many outages come from. Instead: Walk through each step and say what can fail or be slow at each one.
Mistake: Ignoring connection reuse.
Why it is wrong: A new connection per call repeats DNS, TCP and TLS every time, adding several round trips and TLS CPU.
Instead: Share HttpClient through IHttpClientFactory, or a static client with PooledConnectionLifetime.
Mistake: Assuming IPv4 only.
Why it is wrong: Many networks return AAAA records first; if IPv6 is broken somewhere on the path, connects time out before falling back to IPv4.
Instead: Check both A and AAAA answers with dig, and test connectivity on both families when you see slow or failing first connects.
Mistake: Assuming the app server sees the client directly.
Why it is wrong: A CDN, WAF, load balancer or ingress usually terminates TLS, so RemoteIpAddress and the scheme reflect the proxy, not the caller.
Instead: Configure ForwardedHeadersOptions with the known proxies and account for proxy timeouts.
Mistake: Blaming the server for slow calls without splitting the timing.
Why it is wrong: "The API is slow" often turns out to be DNS, TLS or connection setup.
Instead: Measure DNS, connect, TLS and time-to-first-byte separately with curl -w, HttpClient metrics or OpenTelemetry traces.
This question is a map of everything that can go wrong. A strong answer names each hop, its cost and its failure mode.
| Step | Typical cost (cold) | Common failure | How I see it |
|---|---|---|---|
| DNS | 0 to 50 ms | Stale records, resolver timeouts, broken AAAA | dig, System.Net.NameResolution metrics |
| TCP connect | 1 RTT | Firewall timeouts, refused, port exhaustion | ss, connect duration metric |
| TLS | 1 RTT (TLS 1.3) plus CPU | Expired or mismatched certificate, missing intermediate | openssl s_client, TLS handshake metrics |
| Edge (CDN, LB, ingress) | Small | Header rewriting, idle timeouts, buffering | LB logs, X-Forwarded-* |
| HTTP request | 1 RTT plus server time | Server errors, slow queries | Server traces |
| Protocol | Setup | Multiplexing | Head-of-line blocking |
|---|---|---|---|
| HTTP/1.1 | TCP + TLS | No (one request per connection at a time) | Per connection |
| HTTP/2 | TCP + TLS | Yes, many streams | At the TCP level on loss |
| HTTP/3 | QUIC (transport and TLS combined) | Yes | Per stream only |
The lessons are to reuse connections, measure each phase with OpenTelemetry or Application Insights dependency tracking, and know your edge, because proxies change what the app sees.
DNS maps names to records: A (IPv4), AAAA (IPv6), CNAME (alias), plus MX, TXT, SRV, NS and PTR. A client asks a recursive resolver, which walks root → TLD → authoritative servers and caches the answer for its TTL. .NET uses the OS resolver (getaddrinfo) and does not cache itself.
Typical incidents:
HttpClient kept pooled connections to the old IP. Fix with PooledConnectionLifetime.ndots:5 makes each external name try several search domains first, and UDP conntrack races cause occasional 5-second delays.Diagnose with dig/nslookup from the same pod, check TTLs, and look at the DNS phase of the HTTP timing.
HTTPS is HTTP inside TLS. TLS gives confidentiality (encryption), integrity (tamper detection) and server authentication (certificates).
TLS 1.3 handshake (one round trip): the client sends supported ciphers, a key share, SNI (hostname) and ALPN (h2); the server replies with its key share, certificate chain and a signature proving it owns the private key. Both derive session keys via ECDHE (forward secrecy).
The client validates: chain to a trusted root, not expired, hostname matches the SAN, and optionally revocation.
For "remote certificate is invalid": read the SslPolicyErrors / chain status, inspect with openssl s_client -showcerts, then fix the real cause (expired cert, missing intermediate, wrong hostname, or a private/corporate root CA not trusted in the container). Never return true from ServerCertificateCustomValidationCallback in production.
For most APIs, latency × number of round trips dominates, not bandwidth. A request that makes 50 sequential database calls at 2 ms RTT spends 100 ms on the network; move the database to another region at 30 ms RTT and it spends 1.5 s. Fix it by reducing round trips (batching, joins, fewer chatty calls), moving compute near data, caching, and parallelising independent calls.
Last updated 23 Oct 2026.