Skip to content

    Frontend System Design

    DNS, TCP, TLS and the Request Lifecycle

    Trace a browser request through DNS, TCP and TLS, read each phase in Resource Timing, and cut connection setup cost with reuse, HSTS and resource hints.

    By Published 15 min read

    • frontend-system-design
    • dns
    • tls
    • networking
    Share

    Flagship lessonbeginnerInteractive simulatorFrontend System Design

    Before this: Frontend System Design: The Big Picture

    What you'll learn: By the end of this lesson you will be able to describe what the browser does between "a URL is requested" and "the first byte of the response arrives": the name lookup (DNS), the transport connection (TCP) and the encrypted session (TLS). You will be able to read each phase in the Resource Timing API, say which phases a reused connection skips, explain what HSTS, preconnect and dns-prefetch change, and decide how many origins a page can afford.

    Before you can ask a friend a question by phone you look up the number, dial, wait for an answer and check it is really your friend. If you hang up after every question, you repeat all of that each time, and the questions are the smallest part of the call.

    A browser fetching from a server it has never contacted does the same things in the same order. A front-end design decides how often they happen: every extra origin (a scheme, host and port combination, such as https://cdn.example.net) can bring its own look-up, connection and encryption set-up before a byte of your content is requested.

    The running example is a product page at https://shop.example.com/products/1234. Its HTML comes from the shop's origin, its images and script bundle from https://cdn.example.net, a web font from a font provider and an analytics script from a third origin. All host names and numbers are illustrative. The back-end half of the journey is in the system design request lifecycle; here the viewpoint is the browser's.

    The core idea

    Three statements:

    1. A request needs a path before it needs a payload. To send GET /products/1234 over HTTPS the browser needs an IP address (DNS), a transport connection (TCP, or QUIC for HTTP/3) and a secured session (TLS).
    2. The cost of that path is paid per origin, and again per connection. A lookup is cached for a while. A connection is paid once if it is kept open and reused.
    3. Distance multiplies it. Each set-up step costs at least one round trip time (RTT): the time for a message to reach the server and a reply to return. Faster server code does not shrink an RTT. Moving content closer, reusing connections and doing fewer set-ups does.

    How HTTP/1.1, HTTP/2 and HTTP/3 then use the connection is the next lesson, HTTP/1.1, HTTP/2 and HTTP/3 for Front-End Engineers. Answering with no network at all is HTTP Caching for the Front End; what happens in the browser before the URL exists is How a Browser Actually Works.

    The journey as the browser sees it

    Figure 1. A cold HTTPS request, step by step

    sequenceDiagram accTitle: A cold HTTPS request from the browser's point of view accDescr: The browser asks a DNS resolver for an address, opens a TCP connection with a three-way handshake, completes a TLS 1.3 handshake, sends the HTTP request and receives the first byte of the response. participant B as Browser participant R as DNS resolver participant S as Server B->>R: 1. Where is shop.example.com? R-->>B: Address (cacheable for the record's TTL) B->>S: 2. TCP: SYN S-->>B: SYN, ACK B->>S: ACK Note over B,S: one round trip spent on TCP B->>S: 3. TLS: ClientHello (SNI, ALPN) S-->>B: ServerHello, certificate, Finished Note over B,S: one round trip spent on TLS 1.3 B->>S: 4. GET /products/1234 S-->>B: First byte of the response Note over B,S: one round trip plus server time

    How to read this: time runs downward. The first three blocks are preparation and carry none of your page; only step 4 is influenced directly by your code and your server's. Each handshake is simplified to one round trip.

    Two checks can change the picture before step 1: HSTS (below) can rewrite http:// to https://, and a fresh cached copy or a service worker can answer with no request at all. After step 4 the connection stays open, which matters most.

    DNS from the browser's side

    DNS (the Domain Name System) turns shop.example.com into an IP address. RFC 1034 defines a resolver as a program that extracts "information from name servers in response to client requests". In practice the browser hands the question to the operating system or to a recursive resolver (run by an internet provider or a public DNS service), which walks the DNS hierarchy for it. Three facts matter here.

    Answers are cached for a time the domain's owner chooses. Every record carries a TTL (time to live). RFC 1034 says its meaning "is a time limit on how long an RR can be kept in a cache" (an RR is one answer), and "a zero TTL prohibits caching". Resolvers, operating systems and browsers can all cache. A warm lookup is close to free; a cold one costs at least a round trip to the resolver.

    A host can have several addresses. Happy Eyeballs (RFC 8305) says to send the AAAA (IPv6) query first, immediately followed by the A (IPv4) query, and not to wait for both answers before connecting. Its recommended Resolution Delay is 50 ms and one recommended delay between staggered attempts is 250 ms.

    A DNS answer can say more than an address. The HTTPS record type (RFC 9460) can list the protocols a host supports, so it "enables many of the benefits of Alt-Svc" (a response header the next lesson covers) "without waiting for a full HTTP connection initiation". It can also tell a client to use the host only over a secure transport, "similar to HSTS". Browsers decide whether to use it.

    TCP: a reliable connection

    TCP (Transmission Control Protocol) carries HTTP/1.1 and HTTP/2. RFC 9293 says it provides "a reliable, in-order, byte-stream service to applications". Before data flows, the two sides run the three-way handshake, "the procedure used to establish a connection": the client sends a SYN, the server answers SYN and ACK, and the client acknowledges. That costs one RTT.

    The in-order promise has a consequence: when one segment is lost, the data after it waits until the retransmission arrives. That is "TCP head-of-line blocking", and the reason HTTP/3 leaves TCP (next lesson).

    TLS: privacy and identity

    TLS (Transport Layer Security) encrypts traffic, makes it tamper-evident and proves identity: RFC 9110 requires a client to "verify that the service's identity is an acceptable match for the URI's origin server", so a failed certificate check means the browser refuses the connection.

    What the browser sends first. In TLS 1.3 (RFC 8446) the ClientHello carries extensions including server_name (SNI: the host the client wants, so the server can pick a certificate) and application_layer_protocol_negotiation (ALPN, RFC 7301: the client lists the protocols it speaks and the server chooses one). ALPN settles on h2 or http/1.1 inside the handshake with no extra round trip. Afterwards the result appears as nextHopProtocol in Resource Timing: "the network protocol used to fetch the resource, as identified by the ALPN Protocol ID" (MDN).

    The cost. RFC 8446 speaks of a "1-RTT handshake" (in its Figure 1 the client's first application data follows after one round trip) and adds a "0-RTT" mode that saves "a round trip at connection setup for some application data". With 0-RTT the client sends data in its first flight, but "the security properties for 0-RTT data are weaker" and "there are no guarantees of non-replay between connections". The server operator decides whether to accept it; for you, a request that could arrive twice is a reason to make state-changing endpoints idempotent.

    HSTS: removing a redirect

    A user who types shop.example.com, or follows an old http:// link, starts on HTTP, and the server redirects to HTTPS: a full request, then a fresh connection and handshake. The Strict-Transport-Security header removes that on later visits. MDN says it tells browsers that the host "should only be accessed using HTTPS" and that future HTTP attempts "should automatically be upgraded": the browser replaces the scheme itself.

    Strict-Transport-Security: max-age=31536000; includeSubDomains

    max-age is how many seconds the browser remembers. From MDN: the header is ignored over plain HTTP; it cannot help a first visit, which is why a preload list ("not part of the HSTS specification") exists; and on an HSTS host the browser offers no click-through on a certificate error. A judgment, not an MDN statement: includeSubDomains covers every subdomain and is remembered for max-age seconds, so a long value is hard to take back if one subdomain turns out to serve only HTTP. Start short.

    Connection reuse and keep-alive

    The biggest saving is not setting up another connection. RFC 9112 says HTTP/1.1 "defaults to the use of 'persistent connections', allowing multiple requests and responses to be carried over a single connection", and a side that wants to end one sends Connection: close. MDN adds that a TCP connection "adapts to its load", so a warm one performs better than a new one.

    This Node.js script (v26.12.1) sends five sequential requests to a local HTTP/1.1 server and counts the connections it accepts, with and without a keep-alive agent.

    import http from "node:http";
    
    let connections = 0;
    const server = http.createServer((req, res) => res.end("ok"));
    server.on("connection", () => connections++);
    
    const get = (agent) => new Promise((resolve) =>
      http.get({ port: server.address().port, agent }, (res) => { res.resume(); res.on("end", resolve); }));
    
    async function run(label, agent) {
      connections = 0;
      for (let i = 0; i < 5; i++) await get(agent);
      console.log(`${label}: 5 requests, ${connections} TCP connection(s) opened`);
      agent.destroy();
    }
    
    server.listen(0, "127.0.0.1", async () => {
      await run("keep-alive agent", new http.Agent({ keepAlive: true }));
      await run("no keep-alive   ", new http.Agent({ keepAlive: false }));
      server.close();
    });

    Output (identical on three runs):

    keep-alive agent: 5 requests, 1 TCP connection(s) opened
    no keep-alive   : 5 requests, 5 TCP connection(s) opened

    This is loopback: it shows the number of connections, not their cost, which on a real network includes TCP and TLS each time. A browser reuses connections per origin, within two limits. Idle connections are closed eventually, so the first request after a pause may pay set-up again. And under HTTP/1.x a browser opens several connections per origin, because each carries one request at a time: MDN gives "6 parallel connections" as the common number, and each pays its own handshakes. That is one reason HTTP/2 moves to one connection per origin (RFC 9113: clients "SHOULD NOT open more than one HTTP/2 connection to a given host and port pair").

    Reading the phases in the browser

    Resource Timing records a timeline for every resource a page fetches. MDN defines the timestamps: domainLookupStart and domainLookupEnd around the lookup, connectStart and connectEnd around connection set-up, secureConnectionStart "immediately before the browser starts the handshake process to secure the current connection", requestStart, responseStart (the first byte) and responseEnd. Read live entries with performance.getEntriesByType("resource").

    One subtlety decides how you subtract them. The Fetch standard says the connection end time "must include the time interval to complete enough of the TLS handshake to request the resource". So on HTTPS the window connectStart to connectEnd covers TCP and TLS together, TLS is connectEnd - secureConnectionStart, and TCP is the rest. (MDN's list of typical metrics gives TCP as connectEnd - connectStart and TLS as requestStart - secureConnectionStart; on HTTPS the first includes TLS and the second includes the request's own gap, so this lesson follows the Fetch standard. The standard lets a browser report an earlier connection end when it uses TLS False Start or early data, so treat the TLS figure as approximate.) For HTTP/3 the standard says connection start and secure connection start "must be equal", because the secure handshake is part of the initial connection set-up, so there is no separate TCP phase.

    With the made-up entry domainLookupStart: 2, domainLookupEnd: 27, connectStart: 27, secureConnectionStart: 107, connectEnd: 187, requestStart: 188, responseStart: 290 (milliseconds, illustrative), the subtraction gives DNS 25, TCP 80, TLS 80 and a wait for the first byte of 102. Running that arithmetic in Node.js prints { dns: 25, tcp: 80, tls: 80, waiting: 102 }.

    Reused connections read as zeros. The Fetch standard's algorithm "ensures that details of reused connections are not exposed": if the connection started before this request, the lookup and connection timestamps are set to the request's start time. A same-origin phase of 0 usually means reuse.

    A cross-origin catch. For another origin's resources the browser hides most numbers unless that origin opts in. MDN lists the lookup, connect, request and response timestamps, nextHopProtocol and transferSize among the properties that return 0 or an empty string unless the resource passes the Timing-Allow-Origin check, so the CDN or third party must send, for example, Timing-Allow-Origin: https://shop.example.com. Zeros for your own asset domain? Check this first.

    Why origins are the unit of cost

    Every host besides the HTML's own is a separate origin and, normally, a separate DNS answer, connection and TLS session. That is why a page with many third-party hosts is slow in a way file sizes do not explain.

    Figure 2. One page, four origins, four set-ups

    A product page opens separate connections to its own origin, a CDN, a font host and an analytics hostProduct page to Shop origin (DNS, TCP, TLS); Product page to Asset origin; Product page to Font host; Product page to Analytics hostOUR ORIGINSTHIRD PARTIESProduct page: in the browserProduct pagein the browserShop origin: HTML, APIShop originHTML, APIAsset origin: images, bundleAsset originimages, bundleFont host: web fontFont hostweb fontAnalytics host: tracking scriptAnalytics hosttracking scriptDNS, TCP, TLS

    How to read this: each arrow is a complete DNS, TCP and TLS set-up, paid at most once per origin if the connection is reused. The two grey origins are outside your control: you can only decide whether the page needs them early.

    The HTML origin's set-up cannot be hidden. The asset origin's can overlap with parsing if the browser learns about it early, and third parties can often be deferred.

    Cutting the cost

    Resource hints start work early. <link rel="preconnect" href="https://cdn.example.net"> begins connection set-up before the request. MDN says it performs "part or all of the handshake (DNS+TCP for HTTP, and DNS+TCP+TLS for HTTPS origins)" and has no benefit for same-origin requests. <link rel="dns-prefetch"> does only DNS. web.dev advises preconnecting only the most critical origins (the browser closes a connection unused within 10 seconds, and unnecessary preconnecting "can delay other important resources") and using dns-prefetch for the rest. Both are hints the browser may ignore, and web.dev shows preconnect sent as a Link header too.

    Fonts are fetched in anonymous (CORS) mode, and web.dev says that for such resources the preconnect needs crossorigin, or "the browser only performs the DNS lookup". MDN's Early Hints page gives the reason: CORS-protected resources "must be fetched over a completely separate connection".

    <link rel="preconnect" href="https://cdn.example.net">
    <link rel="preconnect" href="https://fonts.example.org" crossorigin>
    <link rel="dns-prefetch" href="https://analytics.example.org">

    Use separate tags for the two hints: web.dev documents a Safari bug where a combined rel="preconnect dns-prefetch" cancels the preconnect.

    Figure 3. Which tool for which origin

    flowchart TD accTitle: Choosing a hint for an origin accDescr: A decision flow for an origin the page uses. If it is the page's own origin, no hint is needed. If it is needed early and critical, use preconnect, adding crossorigin for fonts. If it is needed later, use dns-prefetch. If it is not needed above the fold, load it late or drop it. A["Origin the page uses"] --> B{"Same origin as
    the HTML?"} B -->|"yes"| C["No hint: the
    connection is open"] B -->|"no"| D{"Needed for the first
    render?"} D -->|"yes, critical"| E["preconnect
    (crossorigin for fonts)"] D -->|"yes, but not critical"| F["dns-prefetch"] D -->|"no"| G["Load late or
    remove"]

    How to read this: the most common correct answer is "no hint" or "dns-prefetch". Preconnect is for the few origins that gate the first render.

    Lever What it saves What it costs Watch out for
    Fewer origins DNS, TCP and TLS per removed origin Less flexibility; shared cache policy Cookies and cache policy of the merged origin
    HSTS The http-to-https redirect on later visits A long max-age is hard to undo First visit is not covered unless preloaded
    preconnect Moves set-up off the critical path An unused connection is closed Overuse can delay other resources

    Three illustrations

    A minimal example (the arithmetic). Suppose, as an illustration, a 50 ms round trip, a 30 ms uncached lookup and 20 ms of server time. A cold request pays 30 (DNS) + 50 (TCP) + 50 (TLS 1.3) + 50 + 20 (request) = 200 ms before the first byte. A second request on the open connection pays 70 ms. With a 150 ms round trip (another continent) the cold figure is 30 + 150 + 150 + 150 + 20 = 500 ms: distance multiplied everything except the server's 20 ms.

    A realistic example (the product page). Only once the HTML arrives does the browser discover the asset origin, then the font host from the stylesheet, then the analytics script. Each discovery on a cold page starts a full set-up. The response: preconnect to the asset origin and font host, defer analytics, and put as much as practical behind one origin.

    A failure or edge case (preconnect that does nothing). A team adds <link rel="preconnect" href="https://fonts.example.org"> and sees no change. The font is requested in anonymous mode, so without crossorigin only DNS happens and the connection is still opened late. The fix is one attribute; the network panel is how you check.

    The other side of this topic. The back-end view asks where TLS terminates.

    In practice: a storefront whose images stop loading

    NOTE

    This is a realistic scenario, not a case history. It is a composite built from common production patterns. It describes no specific company or product, and every number in it is an illustrative assumption.

    The situation

    A storefront serves pages from shop.example.com and its images and script bundle from cdn.example.net, on a separate certificate. One morning support reports product pages with text but no images, and dead buttons. The HTML still loads. Another team owns the CDN account.

    Starting architecture

    Figure 4. HTML and assets on separate origins, each with its own certificate

    Starting architecture: the browser reaches the HTML origin and the asset origin through separate connections, each secured by its own certificateShopper's browser to Shop origin (works); Shopper's browser to Asset origin (TLS fails); Asset origin to TLS certificate (presents)ORIGIN A: HTMLORIGIN B: ASSETSShopper's browserShopper'sbrowserShop origin: HTML, APIShop originHTML, APIAsset origin: images, bundleAsset originimages, bundleTLS certificate: expiredTLScertificateexpiredworksTLS failspresents

    How to read this: two origins mean two independent chains of DNS, TCP and TLS. A problem at the TLS step of one chain breaks that origin only, so the page ends up half working.

    Options considered

    Option What it fixes What it costs Risk
    Renew the asset certificate The cause The owning team, now None once done
    Serve assets from the HTML origin Page works again A deploy, rewritten URLs, cache policy Load on the HTML origin

    The decision

    Field Decision record
    Context The asset certificate is rejected, so everything from that origin fails at the TLS step; the HTML origin is healthy.
    Decision Page the owning team to renew, and prepare the fallback (assets from the HTML origin) behind a flag. Do not ask users to bypass warnings.
    Consequences The cause is fixed at its source and a fallback is ready; the fallback duplicates asset hosting and needs cleanup.

    Rollout and verification

    1. Confirm in the network panel that the failed requests all go to cdn.example.net and fail before any response (no status code), while the HTML request succeeded.
    2. After renewal, reload with the cache disabled and watch the asset error rate in field monitoring.

    The 2 a.m. failure

    Three months later the same thing happens at 2 a.m. on an asset host added for a campaign and never put into expiry monitoring. Conversion drops with no application error, because the browser never reaches a server that could log one.

    First three checks: (1) in the network panel, find the phase where the request fails: before the request (TLS, DNS or TCP) points at certificate, name or network, after it points at the server; (2) check the certificate expiry of every host the page uses; (3) check whether the failing host is new. Mitigation: renew the certificate or point asset URLs at a working origin. Follow-up: an inventory of every origin a page uses with expiry alerts, and a synthetic check that fails if any subresource fails at the connection phase.

    Architect's notes

    • Keep an inventory of origins per page. Each is a set-up cost, a certificate to renew and a DNS dependency.
    • Measure phases before adding hints. A hint for an origin that is already fast gains nothing.

    Common mistakes

    • Assuming a faster server fixes a slow first request. For distant users preparation often dominates.
    • Preconnecting everything, or forgetting crossorigin on a font preconnect. The first can delay other resources; the second warms DNS only.
    • Sharding assets across hostnames. MDN calls domain sharding deprecated and, under HTTP/2, "even detrimental to performance".
    • Ignoring certificate expiry on asset and third-party hosts. The HTML can load while the page is unusable.

    Key takeaways

    • A request needs an address (DNS), a connection (TCP, or QUIC) and a secured session (TLS) before the first request byte.
    • DNS answers are cached for the record's TTL; TCP's handshake costs one round trip; TLS 1.3 describes a 1-RTT handshake. HTTP/1.1 connections are persistent by default, and reuse is the largest saving.
    • The unit of set-up cost is the origin, so the number of origins a page touches is a design decision.
    • HSTS removes the http-to-https redirect on later visits but needs one earlier HTTPS response.
    • preconnect warms DNS, TCP and TLS, dns-prefetch only DNS, and fonts need crossorigin on the hint.
    • Resource Timing exposes each phase, but cross-origin values and reused connections read as 0.

    Practice exercises

    Exercise 1 - Starter (30 min): Open a page you work on, list every host it requests from, and note the protocol (nextHopProtocol) and whether each connection was reused.

    Exercise 2 - Intermediate (2 hours): Run the keep-alive script with 20 parallel requests and maxSockets: 2, and explain the connection count.

    Exercise 3 - Advanced (half day): For a page with four or more origins, choose preconnect, dns-prefetch or nothing for each using Figure 3, verify with a throttled recording, and write down what you would revert.

    In this course:

    Elsewhere on the site:

    Official documentation

    Ask about this article

    Get answers grounded in this post. AI-generated — based on this article, and may be imperfect.

    Scaled AI Weekly

    Enjoyed this? Get more like it every Monday.

    Real architecture decisions, LLMOps patterns that survive production, and engineering leadership advice — from 12+ years of building at enterprise scale. Free. No spam. Unsubscribe anytime.

    Get new articles by email

    Free. We email a confirmation link first, and you can unsubscribe from any email.

    Join engineers building production AI systems

    Comments