In short
- Before a browser can show a page it has to find the server's address, open a TCP connection, agree keys with TLS and send an HTTP request, and each of those steps waits for an answer from the other side.
- On a first visit over HTTPS that is a DNS lookup plus three round trips to the server before the first byte of HTML arrives: one for TCP, one for TLS 1.3 and one for the request. A full TLS 1.2 handshake needs one more.
- For an ordinary page, round trips matter more than bandwidth: three of them cost 90 ms when a round trip takes 30 ms and 450 ms when it takes 150 ms, whatever the speed of the line.
- HTTP/2 lets many requests share one connection. HTTP/3 runs over QUIC, which sets up transport and encryption in a single round trip and keeps one lost packet from stalling every download. Neither wins on every network.
- curl's timing variables show where one request spends its time, and the browser's waterfall shows the same for a whole page.
On this page
- The unit that matters: one round trip
- First, the name becomes an address
- The TCP handshake: one round trip to agree to talk
- TLS 1.3: keys, a certificate and one more round trip
- The request: one more round trip, plus the server's time
- HTTP/1.1, HTTP/2 and HTTP/3: what each version changed
- After the HTML: subresources and rendering
- Counting it up: one page at 30 ms and at 150 ms
- Seeing the timings: curl and the browser waterfall
- Where slow pages really lose their time
- Questions
- Sources
Type a name into the address bar and press Enter. If the server is a short hop away, the page starts to arrive in a fraction of a second. Put the same server where each round trip takes 150 milliseconds and the same page, byte for byte, sits blank for about half a second before its first byte shows up. Nothing got heavier. The browser just had to cross the distance several times before it was allowed to ask for anything.
Follow one request from the address bar to the first pixels and you can count every crossing: the DNS lookup, the TCP handshake, the TLS handshake, the request itself, and then a second wave of requests for the stylesheets, scripts and fonts the page turns out to need. Once you can count them, most of the advice about fast websites comes down to one idea: cross the distance fewer times.
The unit that matters: one round trip
A round trip is a message going to the other side and the reply coming back; how long that takes is the round-trip time, or RTT. Almost every step of loading a page is a conversation in which one side cannot carry on until it hears back, so its cost is counted in round trips, not in megabits. Distance, routing and the speed of light in fibre set how long each one takes; the guide to server location and latency covers that part.
Bandwidth barely enters into it for an ordinary page. A 20 KB HTML document is 160,000 bits, which a 1 Gbit/s link sends in 0.16 ms. The three round trips before that document can even be requested cost 90 ms at an RTT of 30 ms and 450 ms at 150 ms. That is why a small site on a fast connection can still feel slow.
First, the name becomes an address
The browser cannot connect to a name, only to an IP address. It checks its own cache first, then asks the operating system, whose built-in resolver is a stub resolver: one that cannot do the whole job itself and hands the question to a recursive resolver, usually run by your ISP or a public DNS service, which is expected to cache the answers it gets.1 When that resolver already has the answer, the lookup costs one round trip to the resolver, which is usually much closer than the website. When it does not, the resolver walks the DNS hierarchy on your behalf and you wait for those extra trips too. How records, TTLs and caching decide which case you hit is the subject of DNS explained.
The answer can carry more than an address. An HTTPS record lets a site say, before the browser connects, that it speaks HTTP/3 or should only be reached over https, which saves the browser from learning it the slow way over a full connection.2
The TCP handshake: one round trip to agree to talk
With an address in hand, the browser opens a TCP connection to port 443. TCP numbers every byte it sends, and before any data moves each side has to tell the other its starting sequence number and hear it confirmed. The client sends a SYN carrying its number; the server replies with a SYN-ACK, which confirms the client's number and carries its own; the client confirms that with an ACK. The RFC calls it the three-way handshake because the server's confirmation and its own SYN travel in one message.3
Three messages, but only one round trip of waiting. The client may send data straight after its ACK,3 and in practice the next thing it sends is the start of the TLS handshake. So TCP costs one RTT before anything useful happens. The server has to keep a little state for every half-open connection while it waits for that ACK, which is exactly what a SYN flood abuses.
TLS 1.3: keys, a certificate and one more round trip
An https address means the conversation must be encrypted and the server must prove who it is before the browser sends anything private. That is TLS's job, and version 1.3 does it in one round trip by having the client guess. Its first message already includes a key share for the key exchange it expects the server to pick, so the server can finish the exchange in its reply instead of asking a question first.4 The steps below go flight by flight, a flight being the messages one side sends before it has to wait for a reply.
ClientHello
The browser lists the ciphers it supports, sends its key share, names the site it wants and lists the application protocols it speaks, such as HTTP/2 and HTTP/1.1.
The server's whole flight
The server answers with its own key share in the ServerHello. From that point both sides can derive keys, so everything after it is encrypted: the chosen protocol, the certificate, a signature proving the server holds the certificate's private key, and a Finished message.
Finished, and the request with it
The browser checks the certificate and the signature, sends its own Finished, and puts the HTTP request in the same flight. The request leaves one round trip after the ClientHello did.
The name in the ClientHello is the Server Name Indication extension, which exists so that one address can host many sites securely.6 The protocol list is ALPN (Application-Layer Protocol Negotiation), and it lets client and server settle on HTTP/2 or HTTP/1.1 inside the handshake without adding a round trip.7 If the client's key share guess is wrong, the server asks it to try again, which costs the extra round trip the guess was meant to save.4
A full handshake in TLS 1.2 and earlier needs two round trips before application data can flow, which RFC 7918 describes as a latency penalty of two round-trip times for protocols in which the client speaks first, as HTTP does.5 The same RFC describes False Start, in which a TLS 1.2 client sends its request before the handshake finishes and gets back to one round trip, but only under conditions it spells out, among them a strong cipher and a key exchange with forward secrecy (so that a key stolen later cannot decrypt a recorded session).5 TLS 1.3 makes one round trip the normal case for every client, which is why the RFC says False Start is not needed there.5
Coming back: resumption and 0-RTT
A browser that has talked to the server before can hold a pre-shared key from the last session. With it, TLS 1.3 lets the client send application data in its very first flight, the mode called 0-RTT, which saves a round trip at a cost. That early data is not forward secret: it is encrypted only under keys derived from the stored key, so whoever steals that key later can read it. The protocol also does not guarantee that it cannot be replayed on another connection.4 RFC 9308 suggests limiting 0-RTT to requests that have no lasting effect at the server, such as fetching a page.8 A request that places an order is exactly what a replayed copy should not be able to repeat.
The request: one more round trip, plus the server's time
Only now does HTTP start. The IETF defines it as a family of stateless request and response protocols: the client sends a method, a target and header fields, and the server answers with a status code, its own header fields and usually a body.9 The first digit of the status code gives its class: 2xx success, 3xx redirection, 4xx a problem with the request, 5xx a problem at the server.9 Written out in the HTTP/1.1 text form, a simple exchange looks like this:
GET / HTTP/1.1Host: example.comAccept: text/htmlAccept-Encoding: gzip, brHTTP/1.1 200 OKContent-Type: text/html; charset=utf-8Cache-Control: max-age=300Content-Length: 20480<!doctype html> ... 20 KB of HTML ...The meaning of a message is defined independently of the HTTP version, so a request can pass from one version to another without changing what it says.9 HTTP/2 carries the same methods, fields and status codes in binary frames instead of lines of text,16 and HTTP/3 maps the same semantics onto QUIC,17 so everything here holds in all three.
The time from sending the request to the first byte of the reply is the time to first byte. Chrome's tools define it as one round trip of latency plus the time the server took to prepare the response.25The second half is where the server stack lives: the web server, the application, the database queries. When that part is slow, the guide to the web server stack is where to look. The first half cannot be optimised away; only moving the server closer shrinks it.
Caching: fresh responses cost no round trip
Caching turns round trips into none. A stored response that is still fresh, for example because the server sent Cache-Control: max-age=300 and five minutes have not passed, can be reused without contacting the server at all.10 A stale one can be checked with a conditional request: the browser sends the validator it holds (an ETag or Last-Modified date), and if nothing changed the server answers 304 Not Modified with no body.9 That still costs a round trip, but no download.
Big responses cost extra round trips
A new TCP connection does not send at full speed straight away. The sender starts with a small congestion window and grows it as acknowledgements come back. RFC 6928, an experimental RFC, proposed raising the starting window to 10 segments, at most 14,600 bytes,11 and Linux adopted that value as its default.12 During slow start each acknowledgement lets the window grow by up to one more segment,13 so it roughly doubles every round trip. With 1,460-byte segments the flights go 10, 20 and 40 segments: 14.6 KB, then 29.2 KB, then 58.4 KB, so a 100 KB response arrives in three flights, two round trips more than one that fits in the first, however fast the line. The same arithmetic is why HTML that fits in about 14 KB arrives in one flight.
HTTP/1.1, HTTP/2 and HTTP/3: what each version changed
All three versions carry the same requests and responses. What changed is how many of them can travel at once, and what happens to the others when one packet goes missing.
HTTP/1.1: one request at a time, six connections
HTTP/1.1 keeps a connection open for further requests by default, so the handshakes are paid once per connection rather than once per file.14 On each connection, though, requests are answered one after another. Pipelining, sending several requests without waiting, exists in the standard, but the server must still return responses in the order they were asked for,14 and browsers leave it switched off.15 Instead they open several connections to the same site, commonly six, to fetch files in parallel.15 Every one of those connections pays its own TCP and TLS handshakes and starts its own slow start, and a seventh file waits in a queue until one of the six is free.25
HTTP/2: many streams on one connection
HTTP/2 lets requests and responses interleave on a single connection as independent streams, and compresses the header fields that HTTP/1.1 repeats in full on every request.16 One connection means one set of handshakes, and a page's forty small files no longer queue behind each other. It also makes the old trick of spreading files over several hostnames, domain sharding, pointless and even harmful.15
The weak spot is underneath. TCP delivers bytes strictly in order and has no idea that the bytes belong to different streams, so when a single packet is lost, every stream on the connection stalls until it is resent, including streams whose data arrived intact.17 The HTTP/2 specification says so itself: it does not address TCP's head-of-line blocking.16 On a clean network that never shows. On a lossy mobile link it can, and one HTTP/2 connection then stalls completely where HTTP/1.1 would have stalled one of its six.
HTTP/3: QUIC instead of TCP
HTTP/3 keeps HTTP/2's idea of streams but moves them into the transport. It runs over QUIC, a protocol whose packets travel in UDP datagrams and which provides streams itself.18 QUIC is reliable per stream, with congestion control across the whole connection, so a lost packet stalls only the streams whose data it carried, and it builds TLS 1.3 into the transport rather than layering it on top.17Its handshake does transport and encryption together, to cut connection setup time: the client's first flight carries the TLS ClientHello, and after one round trip it can send requests.18 TCP plus TLS 1.3 needs two round trips for the same. On a return visit, QUIC can also use 0-RTT and send the request in its first flight.18 A QUIC connection can even survive a change of network, such as a phone moving from Wi-Fi to mobile data, because connection IDs let it move to the new network path.18
There is a catch in how the browser finds out. A site usually announces HTTP/3 in an Alt-Svc header on a response sent over TCP,17 so a browser that has never seen the site makes its first connection over TCP and switches later, unless an HTTPS DNS record told it up front.2And some networks block UDP altogether, somewhere between 3% and 5% of them in the measurements the IETF cites, so browsers must be ready to fall back to TLS over TCP.8
| HTTP/1.1 | HTTP/2 | HTTP/3 | |
|---|---|---|---|
| Runs over | TCP, with TLS for https | TCP, with TLS for https | QUIC over UDP, TLS 1.3 built in |
| Round trips to set up an https connection | 2 with TLS 1.3, 3 with a full TLS 1.2 handshake | 2 with TLS 1.3, 3 with a full TLS 1.2 handshake | 1, or 0 with 0-RTT on a return visit |
| Requests in flight on one connection | One at a time | Many, as streams | Many, as streams |
| A lost packet stalls | That one connection | Every stream on the connection | Only the streams it carried |
| How the browser picks it | ALPN, or the default when nothing else is agreed | ALPN, inside the TLS handshake | Alt-Svc header or HTTPS DNS record |
None of this makes one version faster everywhere. Once a connection is open and the network is clean, a request costs one round trip in every version. The gains from HTTP/3 show up on new connections, on networks that drop packets and on pages with many files; on a network that blocks UDP it is not used at all.
After the HTML: subresources and rendering
The first byte of HTML is not a page. The browser starts parsing as soon as the first chunk arrives, turning the markup into the DOM and the stylesheets into the CSSOM, which together decide what gets painted.19While the main parser works, a preload scanner reads ahead through the HTML it already has and requests the high-priority files it finds: stylesheets, scripts and web fonts.19 On an HTTP/2 or HTTP/3 connection those requests all go out together, so a dozen files found in the HTML cost roughly one round trip between them, plus whatever time their bytes take to arrive.
Two rules decide when anything appears. Stylesheets hold up the first paint. Until the page's CSS has arrived and been parsed, the browser paints nothing, since a rule near the end of the last file could restyle anything above it.20 And a script without async or defer pauses the parsing of the HTML until it has been fetched and run.19 A stylesheet in the head is therefore on the path to the first paint, and so is every round trip it takes to fetch it.
The expensive pattern is the chain. A font named in the CSS cannot be requested until the CSS has arrived and been read, so it starts a full round trip later than everything the HTML named directly. A script that loads another script, which then fetches data, adds a round trip at each step. A file from another origin, such as an analytics script, a font service or a widget, needs its own DNS lookup, TCP handshake and TLS handshake before its request can even leave. In a browser's waterfall, each of these shows up as a step down and to the right.
Two HTML hints break the chains. <link rel="preload"> tells the browser about a file it would otherwise discover late, and it helps most with files referenced from inside CSS, like fonts and background images.21 <link rel="preconnect"> tells it to start the DNS lookup, TCP handshake and TLS handshake to another origin early, so that work overlaps with the HTML instead of following it; it does nothing for your own origin, whose connection is already open.22
Counting it up: one page at 30 ms and at 150 ms
Put the pieces together for a first visit to a small page over HTTP/2, TLS 1.3 and TCP, once for a visitor 30 ms from the server and once for one 150 ms away. The assumptions are illustrative, not measured: the resolver has the name cached and answers in 20 ms, the server spends 50 ms building the HTML, the HTML is 12 KB so it fits in the first flight, the CSS and script are named in the HTML, and the stylesheet loads one web font. Transfer time is left out; for files this small it is a few milliseconds.
| Step | Round trips to the server | RTT 30 ms | RTT 150 ms |
|---|---|---|---|
| DNS lookup, cached at the resolver | (1 to the resolver) | 20 ms | 20 ms |
| TCP handshake | 1 | 30 ms | 150 ms |
| TLS 1.3 handshake | 1 | 30 ms | 150 ms |
| HTML request, plus 50 ms at the server | 1 | 80 ms | 200 ms |
| First byte of HTML | 3 | 160 ms | 520 ms |
| CSS and script, found in the HTML | 1 | 30 ms | 150 ms |
| Font, found in the CSS | 1 | 30 ms | 150 ms |
| Text shown in its real font | 5 | 220 ms | 820 ms |
Illustrative assumptions: 20 ms DNS answered from the resolver's cache, 50 ms of server time for the HTML, small files, no packet loss.
At 30 ms: 20 + 30 + 30 + (30 + 50) = 160 ms to the first byte, then two more round trips of 30 ms for 220 ms. At 150 ms: 20 + 150 + 150 + (150 + 50) = 520 ms, then 2 × 150 for 820 ms. The server's 50 ms is the same in both columns. The 600 ms between them is the extra 120 ms of round-trip time multiplied by five round trips, and no amount of server tuning touches it.
- To the server, from Enter to text in its real font, on a first visit (plus the DNS lookup)
- 5round trips
- The whole sequence with the server 30 ms away
- 220ms
- The same page with the server 150 ms away
- 820ms
Change the protocol and the count changes with it. A full TLS 1.2 handshake adds a round trip, making it 250 ms and 970 ms. HTTP/3, on a site the browser already knows supports it, saves one: 190 ms and 670 ms. A visitor who follows an old http:// link to a site with no HSTS first opens a TCP connection to port 80 and gets a redirect, which adds two round trips before the https connection even starts: 280 ms and 1,120 ms. The drawing below counts only the connection and the first request.
Seeing the timings: curl and the browser waterfall
curl can report when each step of a single request finished through its -w option. The variables that matter here are time_namelookup (name resolved), time_connect (TCP connected), time_appconnect (TLS handshake done), time_starttransfer (first byte received) and time_total.24 This is one real run from a laptop in India to example.net, made while writing this guide, with the name already in the resolver's cache:
curl -so /dev/null -w 'dns %{time_namelookup}\ntcp %{time_connect}\ntls %{time_appconnect}\nfirst byte %{time_starttransfer}\ntotal %{time_total}\nversion %{http_version}\n' https://example.net/dns 0.003851tcp 0.020711tls 0.049155first byte 0.081070total 0.081349version 2The trap is that these are not durations. Every value counts from the moment curl started, so each phase is the difference between two neighbouring lines.24 Here the TCP handshake took 20.711 minus 3.851, so 16.9 ms, and since the handshake takes one round trip, that is a fair measure of the RTT. TLS took 28.4 ms: one round trip plus about 11.5 ms of checking the certificate, computing keys and ordinary jitter. The request took 31.9 ms, one round trip plus the server's work, and the small page itself arrived in 0.3 ms. Run it a few times: the first run pays for any DNS lookup that is not cached yet, and single runs jitter by tens of milliseconds on an ordinary connection.
Reading a browser waterfall
For a whole page, open the browser's developer tools on the Network panel and reload. Every file gets a row, and hovering over its bar shows the phases Chrome measures: Queueing, Stalled, DNS Lookup, Initial connection (the TCP handshake and TLS negotiation together), Request sent, Waiting (TTFB) and Content Download.25Tick Disable cache to see what a first-time visitor sees, since repeat visits are served partly from the browser's cache.25 That switch turns off the HTTP cache and nothing else. It does not close connections or forget DNS answers, so a quick reload reuses the connection that is already open and shows no DNS or connection phase at all. To see a true cold load, open the page in a new private window, or wait until the idle connection has closed before you reload. Then read the shape rather than the individual numbers:
- DNS and connection bars on many rows mean many origins or many connections, each paying its own handshakes.
- A staircase, where each file starts only when another has finished, is a discovery chain: the HTML names the CSS, which names the font, which only then gets requested.
- A long Waiting phase on the HTML with short Waiting phases on static files means the time is being spent building the page on the server, not on the network.
- Long Queueing on an HTTP/1.1 site is the six-connection limit at work: requests waiting for a free connection.25
Where slow pages really lose their time
Each of these either spends round trips that did not need to happen or looks for the lost time in the wrong place.
- Testing from next door. A developer 5 ms from the server sees every round trip vanish. A visitor whose round trip takes 150 ms pays thirty times as much for each one. Measure from where your visitors are, or use the throttling presets in the browser's tools to get closer to their experience.25
- Redirect chains.
http://example.comtohttps://example.comtohttps://www.example.comcan cost a new connection at each hop. Link to the final address everywhere, and send an HSTS header so returning browsers rewrite http to https themselves before loading anything.23 - Too many origins. Every third-party host on the critical path is another DNS lookup, TCP handshake and TLS handshake. Host what you can yourself, and
preconnectto the few origins you cannot avoid. - Resources found late. Fonts and hero images referenced only from CSS or JavaScript start a round trip or two behind everything else.
preloadthem. - Expecting HTTP/3 to fix a slow server. It saves handshake round trips and loss stalls. If your HTML spends 800 ms in the application, the waterfall will still show 800 ms of Waiting.
- No cache headers on static files. Without an explicit freshness lifetime, a returning browser may have to check with the server again for every stylesheet and script. With
Cache-Control: max-ageset, those requests cost nothing at all until the lifetime runs out.10
Before changing anything, run the curl command above from a machine near your visitors and compare two steps. The TCP step is one round trip, the price of distance. The step from tls to first byte is one round trip plus the server's time. If that second step is not much longer than the first, the page is waiting on distance, and only a server closer to your visitors will cut it; if it is several times longer, the server is slow to build the page and the fix is in the application.
Questions
How many round trips does it take to load a website?
On a first visit over HTTPS with TCP and TLS 1.3, three round trips to the server before the first byte of HTML arrives, plus a DNS lookup: one for the TCP handshake, one for TLS and one for the request. The CSS, scripts and fonts the page needs then add at least one more round trip for every step of discovery.
What is time to first byte (TTFB)?
The time between sending a request and receiving the first byte of the reply. Browser tools count it as one round trip plus the time the server spent building the response; curl's time_starttransfer also includes the DNS lookup and the TCP and TLS handshakes before it.
Is HTTP/3 faster than HTTP/2?
Often on a new connection and on networks that lose packets, because QUIC sets up transport and encryption in one round trip and a lost packet only stalls the streams whose data it carried. On a clean connection that is already open, both spend one round trip per request, and where UDP is blocked the browser falls back to TCP anyway.
Why is TLS 1.3 faster than TLS 1.2?
A full TLS 1.2 handshake needs two round trips before application data can flow. An extension called False Start can cut that to one, but only under extra conditions. TLS 1.3 puts the client's key share in its very first message, so the server can answer with everything the client needs in one flight, and the handshake finishes in one round trip.
Does a faster internet connection make websites load faster?
Up to a point. Bandwidth decides how quickly large files arrive, but a typical page spends much of its loading time waiting on round trips, and those depend on distance and the number of steps, not on the speed of your line.
Sources
- RFC 9499: DNS TerminologyIETF
- RFC 9460: Service Binding and Parameter Specification via the DNS (SVCB and HTTPS Resource Records)IETF
- RFC 9293: Transmission Control Protocol (TCP)IETF
- RFC 8446: The Transport Layer Security (TLS) Protocol Version 1.3IETF
- RFC 7918: Transport Layer Security (TLS) False StartIETF
- RFC 6066: TLS Extensions: Extension DefinitionsIETF
- RFC 7301: TLS Application-Layer Protocol Negotiation ExtensionIETF
- RFC 9308: Applicability of the QUIC Transport ProtocolIETF
- RFC 9110: HTTP SemanticsIETF
- RFC 9111: HTTP CachingIETF
- RFC 6928: Increasing TCP's Initial WindowIETF
- include/net/tcp.h (TCP_INIT_CWND)The Linux kernel source
- RFC 5681: TCP Congestion ControlIETF
- RFC 9112: HTTP/1.1IETF
- Connection management in HTTP/1.xMDN Web Docs
- RFC 9113: HTTP/2IETF
- RFC 9114: HTTP/3IETF
- RFC 9000: QUIC: A UDP-Based Multiplexed and Secure TransportIETF
- Populating the page: how browsers workMDN Web Docs
- Critical rendering pathMDN Web Docs
- rel="preload"MDN Web Docs
- rel="preconnect"MDN Web Docs
- RFC 6797: HTTP Strict Transport Security (HSTS)IETF
- curl man page: --write-out variablesThe curl project
- Network features reference: timing breakdown phasesChrome for Developers