paulserban.eu

Writing Edition

Paul Serban

Writing, snippets & book notes

Internet: How Does The Internet Work

From DNS Resolution to TCP Handshakes - Understanding the Systems Behind Every Request

Introduction: The Machinery Behind a Single Request

Every time a browser sends a request to example.com, a chain of independent systems cooperates to make that request succeed in under a second. None of those systems know about each other's internal state. There is no central coordinator, no single company that owns the whole path, and no guarantee that any two packets in the same conversation travel the same physical route. Yet the system works, reliably, at a scale of billions of devices. That reliability is not an accident - it's the product of layered protocols designed decades ago to survive exactly the kind of unpredictability the internet exposes them to.

For most developers, the internet is an abstraction hidden behind fetch(), axios.get(), or a load balancer's health check. That abstraction is useful until it isn't - until a request times out, a certificate fails to validate, a CDN serves stale content, or DNS propagation delays a deployment. This article opens up that abstraction layer by layer, focusing on the mechanisms an engineer actually needs to reason about when debugging production systems: name resolution, transport reliability, encryption, and the application protocols riding on top of them.

Context: Why "The Internet" Is Actually a Network of Networks

The word "internet" is short for internetwork - a network connecting other networks. This is not a marketing simplification; it is the literal architectural principle. Your home network, your ISP's regional network, a long-haul fiber backbone, and the data center hosting a target website are all independently operated, independently managed networks. They agree to interconnect using a common set of protocols - most importantly the Internet Protocol (IP) - and exchange routing information about how to reach each other's address space through the Border Gateway Protocol (BGP).

This decentralization is deliberate. The internet's predecessor, ARPANET, was designed with packet switching specifically so that no single point of failure could take down the whole network. Instead of establishing one fixed circuit between two endpoints (as a traditional telephone call did), data gets broken into discrete packets, and each packet is routed independently, sometimes over different physical paths, and reassembled at the destination. If one router fails or a link is congested, packets simply route around it. This is why a single service outage rarely takes down "the internet" globally, but it's also why a bad BGP route announcement - as has happened in several well-documented outages - can misroute traffic for large chunks of the internet at once.

Understanding this network-of-networks structure matters practically because it explains latency behavior that engineers often misattribute to application code. A request from Tokyo to a server in Virginia doesn't fail because your API is slow; it fails because it's traversing a dozen or more autonomous systems, each adding queuing delay, and the physical speed of light imposes a hard floor of roughly 67 milliseconds round-trip for that distance alone. No amount of application-level optimization changes the physics of the path - which is exactly why CDNs, edge computing, and regional deployments exist as architectural patterns rather than optimizations.

The Layered Model: How Protocols Stack on Top of Each Other

Networking is commonly taught through two conceptual models: the seven-layer OSI model and the four-layer TCP/IP model that the actual internet is built on. In practice, engineers work primarily with four layers: the link layer (Ethernet, Wi-Fi - moving frames between physically adjacent devices), the network layer (IP - routing packets between networks), the transport layer (TCP or UDP - delivering data between processes), and the application layer (HTTP, DNS, SMTP, and so on - the protocols your code actually speaks). Each layer only needs to understand the interface of the layer directly below it, which is what allows Wi-Fi to be swapped for fiber, or IPv4 for IPv6, without rewriting every application on earth.

IP itself provides no guarantees. It is a best-effort, connectionless protocol: packets can arrive out of order, duplicated, or not at all. This is intentional - IP's job is only to get a packet as far as it can toward a destination address, not to guarantee delivery. That guarantee, when needed, is the job of the transport layer. TCP (Transmission Control Protocol), defined originally in RFC 793 and refined since, layers a connection-oriented, ordered, and reliable delivery model on top of IP's unreliable one. It does this through sequence numbers, acknowledgments, and retransmission timers, plus congestion control algorithms that throttle the sending rate when packet loss suggests network congestion.

The famous TCP three-way handshake - SYN, SYN-ACK, ACK - is the mechanism by which two endpoints agree to open a reliable channel before any application data flows. This is also why TCP has an inherent latency cost: at least one full round trip must complete before the first byte of actual data can be sent, and if the connection is encrypted with TLS, additional round trips are added on top for the cryptographic handshake. UDP, by contrast, skips all of this - it sends packets with no handshake and no delivery guarantee, which is why it's preferred for DNS lookups, video streaming, and gaming, where a dropped packet is cheaper to tolerate than the latency of guaranteeing its delivery.

DNS sits at the application layer but functions almost like connective tissue for everything above it. Defined primarily in RFC 1035, DNS is a distributed, hierarchical database mapping human-readable names to IP addresses. When a resolver doesn't have a cached answer, it queries a root server, which redirects it to the appropriate TLD server (e.g., for .com), which redirects it to the authoritative name server for the specific domain. This hierarchy is why DNS scales to billions of lookups a day without a single database bottleneck - each layer only needs to know about the layer directly below it, and aggressive caching at every step (browser, OS, ISP resolver) means most lookups never touch the root servers at all.

Walking Through a Request: What Actually Happens When You Load a Page

It helps to trace one concrete request end-to-end, because each step above maps to an observable event a developer can inspect with tools like curl -v, browser DevTools' Network tab, or dig. Say a browser needs to load https://example.com/path.

First, DNS resolution happens. The OS checks its local cache; if empty, it queries a configured resolver (often the ISP's, or a public one like 1.1.1.1 or 8.8.8.8). That resolver performs the recursive lookup described above and returns an IP address, which itself gets cached according to the DNS record's TTL (time-to-live) value - a detail that matters enormously in production, since a DNS TTL that's too long will delay failover during an incident, and one that's too short will increase load on authoritative servers and add latency to every cache miss.

Second, the TCP handshake establishes a connection to that IP address on port 443. Third, because this is HTTPS, a TLS handshake follows - as of TLS 1.3 (RFC 8446), this has been reduced to a single round trip in the common case, down from two in TLS 1.2, specifically because handshake latency was recognized as a real-world performance cost worth optimizing. During this handshake, the client verifies the server's certificate against a trusted certificate authority chain, and both sides negotiate a shared symmetric key used to encrypt everything that follows. Fourth, only now does the actual HTTP request go out - a plaintext-structured message (verb, path, headers, optional body) that is encrypted in transit by the TLS layer beneath it, defined by the HTTP semantics in RFC 9110 (which superseded the older RFC 7230-7235 series).

The server processes the request - possibly hitting a load balancer, an application server, a database, and a cache along the way - and returns an HTTP response with a status code, headers, and body. The browser then parses that body, discovers additional resources (CSS, JS, images), and repeats large parts of this process, often in parallel, for each one. Modern protocols like HTTP/2 and HTTP/3 exist specifically to reduce the overhead of doing this dozens of times per page load, by multiplexing multiple requests over a single connection (HTTP/2) or replacing TCP with QUIC, a UDP-based transport that avoids head-of-line blocking and reduces handshake latency further (HTTP/3).

Practical Implementation: Observing the Stack From Code

Most of the time, application code interacts with the network through high-level abstractions - fetch, requests, an ORM's connection pool - that hide every layer discussed above. But it's useful to occasionally drop down a level to see what's actually happening, both for debugging and for building intuition about where latency comes from.

The following Node.js example uses the built-in dns and http modules to separate DNS resolution time from connection and response time explicitly, which is exactly the kind of breakdown you'd want when diagnosing whether a slow endpoint is a DNS problem, a network problem, or an application problem:

import { lookup } from "node:dns/promises";
import http from "node:http";
import { performance } from "node:perf_hooks";

async function timedRequest(hostname: string, path: string): Promise<void> {
  const dnsStart = performance.now();
  const { address } = await lookup(hostname);
  const dnsEnd = performance.now();

  const requestStart = performance.now();

  const req = http.request(
    { host: address, path, headers: { Host: hostname }, port: 80 },
    (res) => {
      let firstByteTime: number | null = null;
      let data = "";

      res.on("data", (chunk) => {
        if (firstByteTime === null) {
          firstByteTime = performance.now();
        }
        data += chunk;
      });

      res.on("end", () => {
        const requestEnd = performance.now();
        console.log(`DNS lookup:        ${(dnsEnd - dnsStart).toFixed(1)}ms`);
        console.log(`Time to first byte: ${(firstByteTime! - requestStart).toFixed(1)}ms`);
        console.log(`Total transfer:     ${(requestEnd - requestStart).toFixed(1)}ms`);
        console.log(`Status: ${res.statusCode}, Bytes: ${data.length}`);
      });
    }
  );

  req.on("error", (err) => console.error("Request failed:", err.message));
  req.end();
}

timedRequest("example.com", "/");

This pattern - separating DNS time, connection/TLS time, time-to-first-byte, and full transfer time - is exactly what the Navigation Timing API exposes in browsers (performance.getEntriesByType("navigation")), and what tools like curl -w and WebPageTest surface for production debugging. Understanding which of these four buckets a slow request falls into determines whether the fix is a DNS TTL change, a TLS session resumption strategy, a backend performance issue, or a payload size problem - four completely different remediation paths that look identical from the symptom of "the page is slow."

Trade-offs and Common Pitfalls Engineers Run Into

The abstraction the internet provides is powerful, but it leaks in predictable ways, and engineers who don't understand the underlying layers tend to misdiagnose the same handful of problems repeatedly. DNS caching is the most common one: a service migration or IP change doesn't take effect instantly everywhere, because resolvers, browsers, and even some operating systems cache records according to TTL - and some intermediate resolvers ignore TTLs more aggressively than the spec suggests. This is why production runbooks for DNS cutovers typically lower TTLs well in advance of a planned change, rather than relying on the change propagating immediately.

TCP and TLS handshake costs are another frequent blind spot. Each new TCP connection costs at least one round trip before any data moves, and a fresh TLS handshake adds another. This is why connection reuse (HTTP keep-alive), connection pooling, and TLS session resumption exist as optimizations - and why opening a new connection per request, a mistake common in poorly configured HTTP clients or serverless functions with cold-started connection pools, can dominate latency far more than anything happening in application logic. Similarly, engineers sometimes assume that because a request "worked," the network path was stable; in reality, TCP's retransmission and reordering logic can mask significant packet loss that only becomes visible as elevated tail latency (p99) rather than outright failure.

Best Practices for Building on Top of the Network

Given these dynamics, a few practices consistently pay off for teams operating internet-facing systems. Keep DNS TTLs proportional to how quickly you need to be able to fail over - a common pattern is longer TTLs (hours) for stable infrastructure and short TTLs (60 seconds or less) for records tied to failover-critical endpoints like load balancers, accepting the added query volume as the cost of agility. Reuse connections wherever the client library allows it; most modern HTTP clients (Node's http.Agent with keepAlive: true, Python's requests.Session, or connection-pooling database drivers) support this, and the latency savings from avoiding repeated handshakes are often larger than any code-level optimization available to the application layer.

Instrument the network boundary explicitly rather than treating it as a black box. Structured logging or tracing that separately records DNS resolution time, connection establishment time, TLS handshake time, and time-to-first-byte turns "the API is slow" from a guessing game into a five-minute diagnosis. Finally, design for the internet's actual failure modes - partial connectivity, regional outages, and inconsistent routing - rather than assuming requests either fully succeed or fully fail. Timeouts, retries with backoff, and circuit breakers exist specifically because packet loss and partial network partitions are normal operating conditions for a global, decentralized network, not edge cases.

Mental Models That Make the Network Click

A useful analogy for the layered model is a postal system. The link layer is the truck moving mail between two adjacent post offices - it doesn't know or care what's inside the envelope. The IP layer is the address on the envelope: it's enough information to route the letter toward its destination, hop by hop, but there's no guarantee the envelope arrives, arrives once, or arrives in order relative to other letters you sent. TCP is the practice of numbering your letters and having the recipient mail back a confirmation for each one - if letter three never gets confirmed, you resend it. DNS, in this analogy, is simply the phone book that turns "the bakery on Elm Street" into a specific street address the postal system can actually route on.

This mental model clarifies why certain engineering decisions make sense. Choosing UDP over TCP is choosing to skip the "send a confirmation for every letter" step because, for a live video call, a lost frame is cheaper than the delay of demanding and waiting for its retransmission. Choosing a CDN is choosing to open a post office much closer to the recipient, so the truck ride - the part with a physical speed limit - is as short as possible. And a DNS outage taking down a functioning web server is the equivalent of every phone book in the country disappearing at once: the bakery didn't move, but nobody can figure out its address anymore.

Another useful frame is thinking of the internet as a series of trust boundaries rather than a single trusted pipe. Every hop - your device, your Wi-Fi router, your ISP, transit providers, the destination's data center - is a separate administrative domain that could, in principle, inspect, delay, or drop your traffic. TLS exists precisely because that path can't be assumed to be trustworthy end-to-end; encryption moves the trust boundary from "every network operator on the path" to "the two endpoints and the certificate authority they both trust."

Key Takeaways

Conclusion: Treating the Network as an Engineering Surface

The internet's apparent simplicity - type a URL, get a page - is the result of decades of careful protocol layering designed to hide enormous underlying complexity from the application developer. That's a genuine engineering achievement, and most of the time, the abstraction should be trusted rather than second-guessed. But when things go wrong - a slow endpoint, a failed deployment, a certificate error, a DNS cutover that didn't propagate as expected - the fix almost always requires stepping down one or two layers to see what's actually happening on the wire.

Treating DNS, TCP, TLS, and HTTP as an engineering surface rather than an inert utility is what separates a developer who can only report "the internet is slow" from one who can say precisely which of four measurable stages is responsible, and why. That precision is not academic; it directly shapes decisions about caching strategy, connection pooling, failover design, and where in the world to physically place a server. The protocols underneath haven't changed much in decades - which means the investment in understanding them keeps paying off long after any particular framework or platform has been replaced.

References