
What Actually Happens When You Enter a URL
The question sounds simple: what actually happens when you enter a URL? The usual answer is a familiar sequence: DNS, TCP, TLS, HTTP, render. That sequence is correct, but knowing the names of the stages is not the same as understanding what happens inside them. Each stage has its own job, its own delays, and its own ways of failing. Follow the complete journey and you get something much more useful: a map of how a website actually reaches your screen.
1. The Basics
You type an address, press Enter, and a page appears. Between those two moments your computer performs a sequence of independent operations, most of them involving machines owned by other people, in other countries, running software you have never heard of. Any one of them can be slow. Any one of them can fail. And they fail in ways that look identical from the outside: a page that will not load.
Every website problem you will ever debug lives in one of these stages. Knowing which one is most of the work.
This article walks the whole journey once, then walks it again with real tools and real output, and ends inside the browser where most of the time is actually spent. It is the hub article for this section: each stage below is a large subject of its own, and this page is the map that shows how they connect.
The right mental model: this is not one operation. It is eight or nine subsystems in a chain, each with its own failure mode, its own timeout, and its own cache. "The site is down" is not a diagnosis. It is a description of the last link.
1.1 The Whole Journey on One Page
Here is everything, in order. Do not memorise it; the rest of the article takes it apart.
you press Enter
↓
1. is this even a URL? browser decides: address or search box
↓
2. do I already know this? HSTS list, DNS cache, connection pool,
HTTP cache - any of these can end the
journey here, with no network at all
↓
3. what is the IP address? DNS: resolver → root → TLD → authoritative
↓
4. how do I get there? routing table → gateway → NAT → hop by hop
and: IPv4 or IPv6? (Happy Eyeballs races them)
↓
5. open a connection TCP handshake (or QUIC, which skips it)
↓
6. agree on encryption TLS handshake: SNI names the host,
certificate checked
↓
7. ask for the page HTTP request
↓
8. something does the work CDN edge, or proxy, or the origin server
↓
9. the response comes back status code, headers, then the body
↓
10. the browser builds the page parse → DOM + CSSOM → layout → paint
↓
11. and does it all again for every image, stylesheet, script and
font the page asked for
Step 11 is the one people forget. A page is not a document, it is a shopping list. The first response usually contains no images and no styling, only instructions to go and fetch them, and each of those fetches repeats steps 2 to 8.
1.2 What This Buys You
Naming the stages is not trivia. It converts a vague complaint into a location, and every diagnostic tool in this article exists to answer the same question: which stage?
| The symptom | Stages that could cause it | Stages that cannot |
|---|---|---|
| "The site is down" | DNS, TCP, TLS, server | Rendering: nothing arrived to render |
| "It works for me but not for them" | DNS caching, HSTS, cookies, CDN edge | The server code, usually |
| "It is slow" | Any of them, which is why you measure | - |
| "The padlock is missing" | TLS only | DNS, server, rendering |
| "Blank white page" | Server error, or rendering | DNS, TCP, TLS: they all succeeded |
That last row is the useful one. A blank page proves the first five stages worked. Somebody spending an afternoon on DNS because a page renders blank is debugging a stage that has already demonstrably succeeded.
1.3 What a URL Is, Briefly
The address you typed is structured, and the browser reads it in parts:
https://www.example.com:443/shop/socks?colour=red#reviews
│ │ │ │ │ │
scheme host port path query fragment
└─── authority ───├────├ ├ ├
│ │ └─ NEVER sent
used to find the └─────────├ to the server
server │
sent to the server in the request
Three parts do three different jobs. The authority tells your computer which machine to talk to. The path and query are what you ask that machine for. The fragment is for the browser alone, and section 7.1 explains why that matters more than it sounds.
The full anatomy of a URL, including the parts that cause encoding bugs, is a subject of its own and gets its own article.
Back to top2. Where the Name Comes From
"URL" stands for Uniform Resource Locator, and the interesting part is the first word, because it was originally a different one.
In June 1994, Tim Berners-Lee published RFC 1630, titled "Universal Resource Identifiers in WWW". Six months later, in December 1994, RFC 1738 appeared, written by Berners-Lee together with Larry Masinter and Mark McCahill, and it was titled "Uniform Resource Locators (URL)".
June 1994 RFC 1630 UNIVERSAL Resource Identifiers in WWW
December 1994 RFC 1738 UNIFORM Resource Locators (URL)
Universal became Uniform in half a year. The difference is not cosmetic. "Universal" is a claim about reach: this identifies anything, anywhere. "Uniform" is a much smaller and much more defensible claim: these all have the same shape. Standards bodies tend to prefer the promise they can keep.
The second word is worth as much attention. A Locator says where something is, which means it stops being valid when the thing moves. That is not a flaw in the design, it is the design, and it is the direct cause of every dead link you have ever clicked. The alternative was specified too - a Resource Name, which identifies without locating - and it never displaced the locator, because knowing what something is called does not tell your computer where to go.
Today the umbrella term is URI, defined in RFC 3986 of January 2005, which is still the current specification.
| Term | Means | Example |
|---|---|---|
| URI | Uniform Resource Identifier: the general idea | Either of the below |
| URL | A URI that says where something is | https://example.com/page |
| URN | A URI that says what something is, with no location | urn:isbn:0451450523 |
In practice almost everything you meet is a URL, and the specification's own advice is that arguing about which term applies is rarely worth the effort. "URL" is what people say, and it is right nearly every time.
Back to top3. A Short History
The journey in section 1.1 was not designed. It accumulated, one layer at a time, and several of its stranger properties are fossils of decisions made when the web was a document-sharing project for physicists.
Before the table, a word about the documents it keeps citing, because this article quotes eleven of them and section 8 tells you to go and read them.
New protocols and features on the internet are proposed, argued over and finally written down in RFCs. The letters stand for Request for Comments - not request for change, and the difference is the whole story. They are published by the IETF, the Internet Engineering Task Force, through the RFC Editor. There are more than nine thousand of them, every one is free to read, and none of them has ever been behind a paywall.
The name is an accident that stuck. RFC 1 was written by Steve Crocker on 7 April 1969, about software for the ARPA Network. Crocker later explained that his group was "informal, junior and unchartered", and that he chose the words deliberately: "I wanted to emphasize these notes were the beginning of a dialog and not an assertion of control." Fifty years on, the documents that define how the entire internet works are still called requests for comments, because nobody wanted to be the person who declared them something grander.
That humility is not only decorative. An RFC has no legal force whatsoever. Nobody is obliged to implement one, and no authority can compel them to. They are followed because a protocol that only you speak is worthless, which turns out to be a stronger incentive than a rule would be.
One property matters more than any of that when you go looking things up. An RFC is never revised. Once published it is frozen, permanently, exactly as written. When the rules change, a new RFC is published with a new number, and it declares in its header what it did to its predecessors:
Obsoletes: 7540 this REPLACES that document entirely
Updates: 2026 this CHANGES part of that document,
which otherwise still stands
Which produces the trap worth knowing about, and this article walked into it while being written. RFC 2026 defines how something becomes an internet standard, and describes three maturity levels a specification passes through. Read it today and you would state that confidently and be wrong. RFC 6410, of October 2011, reduced them to two, and says so only in its own header: Updates: 2026. RFC 2026 itself was never touched and still says three.
So the habit to build is small and saves real embarrassment: look at the header before you trust the body. Every RFC page on rfc-editor.org shows, at the top, whether the document has been obsoleted or updated and by what. A specification from 1999 that nobody has superseded is current; one from last year that has already been obsoleted is history. The date tells you nothing on its own.
One last thing that surprises people: not every RFC is a standard. The series also carries Informational, Experimental, Best Current Practice and Historic documents, and every 1 April it carries jokes, formally published and numbered alongside the rest.
| Date | Milestone |
|---|---|
| 1983 | DNS is introduced, replacing a single shared HOSTS.TXT file that every machine had to download |
| March 1989 | Berners-Lee circulates the proposal at CERN that becomes the World Wide Web |
| 1991 | The first website goes live. HTTP has one method, GET, and no headers, no status codes and no encryption |
| June 1994 | RFC 1630: "Universal" Resource Identifiers |
| December 1994 | RFC 1738: URLs get their standard, and their final name. Netscape ships SSL the same year |
| 1997 | HTTP/1.1: persistent connections, so a page stops opening a new connection per image |
| January 2005 | RFC 3986 defines URI generic syntax; still current today |
| 2010 onward | HTTPS moves from "for checkout pages" to "for everything", pushed by free certificates and by browsers marking plain HTTP as not secure |
| May 2015 | HTTP/2: one connection carries many requests at once |
| August 2018 | RFC 8446, TLS 1.3: fewer round trips, and a large amount of old cryptography deleted rather than deprecated |
| June 2022 | RFC 9114 (HTTP/3) and RFC 9113 (HTTP/2). HTTP/3 abandons TCP entirely and runs on QUIC, over UDP. The same month, RFC 9110 rewrites HTTP's semantics as a document separate from any version |
Two entries explain more than the rest.
Persistent connections in 1997 are why step 2 of the journey can end it. Before them, every single image on a page meant a fresh connection, complete with handshake. Once a connection could be reused, the whole economics of a page load changed, and every optimisation since has been some version of "do not pay for the connection again".
The 2022 split matters because HTTP stopped being one thing. RFC 9110 defines what a 404 means and what GET promises, independently of how the bytes travel. HTTP/1.1, HTTP/2 and HTTP/3 are now three ways of moving the same semantics, which is why a modern site can serve all three and behave identically on each.
4. The Journey, Step by Step
Now the same sequence slowly. Each subsection is one stage, what it does, and what it looks like when it goes wrong.
4.1 Step 0: Is That Even an Address?
Before anything touches the network, the browser decides whether you typed an address or a search. Modern browsers use one box for both, so petermartin.nl becomes a URL and peter martin joomla becomes a search.
Ambiguous input goes to the search engine, which is why an internal hostname such as intranet or a typo in a domain often produces search results instead of an error. Nothing failed. The browser decided you were searching.
The browser also fills in what you left out: no scheme becomes https:// on any modern browser, no path becomes /, and no port becomes 443 for HTTPS or 80 for HTTP.
4.2 Step 1: The Caches That End the Journey Early
The fastest request is the one that never happens, and the browser checks four things before it will touch the network:
| Check | If it hits |
|---|---|
| HSTS list | The browser rewrites http:// to https:// itself, before any request. See section 7.2 |
| HTTP cache | A fresh copy is already on disk. The page appears with no network at all |
| DNS cache | The address is known, so stage 3 is skipped |
| Connection pool | A connection to that server is already open, so stages 4 and 5 are skipped |
| Service worker | If the site registered one, it can answer the request itself, from local storage, with no network at all - and can do so while the device is offline |
This is why the second visit to a site feels so different from the first, and why "it works for me" is such a common and such a useless report. Your machine is answering from caches the other person does not have.
A cached copy that has gone stale does not have to be downloaded again either. The browser can ask whether it is still valid, quoting the identifier the server gave it last time:
server said ETag: "abc123"
browser later asks If-None-Match: "abc123"
server answers HTTP/1.1 304 Not Modified (and no body)
A 304 is a full request and response - it still costs a round trip - but it carries no content. That is the difference between revalidating and re-downloading, and it is why a page can feel instant while still making dozens of requests.
4.3 Step 2a: DNS, or Turning a Name into a Number
Computers route to numbers, not names, so petermartin.nl has to become an IP address. Your machine asks a resolver, usually run by your internet provider or by a public service, and the resolver walks a chain:
resolver → root servers "who handles .nl?"
→ .nl TLD servers "who handles petermartin.nl?"
→ authoritative NS "what is the address of petermartin.nl?"
→ the answer, cached for as long as its TTL allows
Three levels, each of which only knows who to ask next. Nobody holds a list of every domain on the internet; the system works by delegation, which is why it has survived going from a few hundred hosts to a few hundred million.
When it fails: the browser reports that it cannot find the server. The name resolved to nothing, so there was never anywhere to connect to. If a site is unreachable and its address is wrong or missing, no amount of restarting the web server will help, because the web server was never contacted.
4.4 Step 2b: Getting There
An IP address is not a route. Knowing the server is at 23.88.98.40 tells your machine nothing about how to reach it, and this is the stage almost every explanation of this subject skips.
Your computer compares the destination against its own routing table. If the address is on the local network it can be reached directly; if not - and it almost never is - the packet goes to the default gateway, which is your router. One command shows the whole decision:
$ ip route get 23.88.98.40
23.88.98.40 via 192.168.4.1 dev wlp0s20f3 src 192.168.7.225
Three facts in one line: send it via the router at 192.168.4.1, out of the wireless interface, and label it as coming from 192.168.7.225. That source address is private. It does not exist on the public internet, which raises an obvious question about how any reply finds its way back.
The answer is NAT, Network Address Translation. The router rewrites the packet to use its own public address and a port it picks, remembers the mapping, and reverses the substitution on every reply.
192.168.7.225:53124 your laptop, private address
↓
home router rewrites the source, remembers the mapping
↓
203.0.113.50:62001 a public address the internet can route to
From there the packet is passed from router to router. No machine anywhere knows the full path. Each one consults its own table, forwards the packet one hop closer, and forgets about it. The tables themselves are built by networks advertising to each other which addresses they can reach, using BGP, the Border Gateway Protocol.
This matters for a practical reason. Nothing in this stage is under your control, and nothing in it reports errors to you. A route that disappears looks exactly like a server that is down, and traceroute or tracepath is the only tool that shows you where the packets stopped.
4.5 Step 2c: Which Address, When There Are Two?
A hostname can resolve to both an IPv4 address and an IPv6 address, and they are different routes to the same place. petermartin.nl has both:
$ dig +short petermartin.nl A
23.88.98.40
$ dig +short petermartin.nl AAAA
2a01:4f8:c0c:6848::1
The machine this article was written on cannot use the second one (IPv6) at all:
$ curl -6 -sS -o /dev/null https://petermartin.nl/
curl: (7) Failed to connect to petermartin.nl port 443 after 24 ms
$ curl -4 -sS -o /dev/null -w '%{remote_ip}\n' https://petermartin.nl/
23.88.98.40
The server offers IPv6, the network does not carry it, and the site loads perfectly anyway. That is not luck. It is Happy Eyeballs, defined in RFC 8305 of December 2017, which exists precisely for this situation: the client starts both lookups, then races connection attempts rather than waiting for one to fail.
Without it, a browser that preferred IPv6 on this network would wait for a full connection timeout on every single site before falling back. The algorithm turns a broken address family from an outage into a few wasted milliseconds, which is why almost nobody knows their IPv6 is broken.
It also explains a class of bug report that otherwise makes no sense. When a site is reachable for most people and not for one person, and the difference disappears when they use a phone on mobile data, the usual cause is one address family working and the other not.
4.6 Step 3: TCP, or Opening the Line
With an IP address in hand, the browser opens a connection. Over TCP that is a three-message exchange, one full round trip before a single byte of your request can be sent.
browser → SYN "can we talk?"
browser ← SYN-ACK "yes, can we talk?"
browser → ACK "yes"
now the connection exists
When it fails, the two failure modes say different things, and this distinction is worth memorising:
- Connection refused means a machine answered and said no. Something is there; nothing is listening on that port. Usually the service is stopped.
- Connection timed out means nothing answered at all. Usually a firewall dropping packets silently, or the wrong address.
Refused is a conversation. Timed out is silence. They point at different teams.
4.7 Step 4: TLS, or Agreeing How to Encrypt
For an https:// address, the connection is now open but unprotected. The TLS handshake fixes that, and it does three separate jobs that people tend to collapse into "the padlock":
- Agree on cryptography: which cipher, which key exchange.
- Prove identity: the server presents a certificate, and the browser checks that it was issued by an authority it trusts, that it has not expired, and that it is actually for this hostname.
- Establish keys that only these two parties know.
TLS 1.3 completes this in one round trip, and can resume an earlier session in zero. Older versions took two, which on a slow mobile connection was a visible delay.
Step 2 hides a chicken-and-egg problem worth knowing about. One IP address commonly serves hundreds of sites, so the server must choose which certificate to present - but the Host header that would tell it lives inside the HTTP request, which cannot be sent until encryption is already set up.
SNI, Server Name Indication, breaks the deadlock: the client names the host it wants inside the TLS handshake itself, before encryption exists. RFC 6066 states the problem plainly - "TLS does not provide a mechanism for a client to tell a server the name of the server it is contacting" - and SNI is the extension that fixed it. Without it, HTTPS on shared hosting would be impossible, which is why it is a hard requirement rather than an option.
Being sent before encryption exists means SNI travels in the clear, and section 7.8 comes back to what that gives away.
When it fails, the browser interrupts with a warning rather than a blank page, and the warning names the reason: expired certificate, wrong hostname, untrusted issuer. Read it. These three causes need three different fixes, and the browser has already told you which one applies.
One of those three is worth treating differently from the other two, because it is the only failure on the whole journey with a known date attached. I monitor certificate expiry separately from uptime for the sites I look after, and the reason is that uptime monitoring cannot help here: by the time a check fails, the certificate has already expired and visitors are already seeing the warning. Watching the expiry date instead turns an outage into three weeks of notice, and an expired certificate is always a renewal-automation failure rather than bad luck.
4.8 Step 5: The Request, and the Response
Finally, the actual question. Over HTTP/1.1 it is readable text; over HTTP/2 and HTTP/3 the same information is sent in a compressed binary form, but the meaning is identical.
GET /en/ HTTP/1.1
Host: petermartin.nl
User-Agent: Mozilla/5.0 (...)
Accept: text/html,...
Accept-Encoding: gzip, br
Cookie: ...
Note Host. One IP address usually serves many sites, so the address got you to the machine and this header tells it which site you actually want. Without it, shared hosting could not exist.
One thing is worth saying about the machine that answers, because it is often not the one you think. If the site uses a CDN, DNS will have pointed you at an edge server near you rather than at the origin, and that edge may answer entirely from its own cache. The origin server is never contacted, may be on another continent, and may have been switched off for maintenance without anyone noticing. That is the intended behaviour, and it is why a change you deployed can be invisible for hours.
The server - whichever one it is - then does its work, and answers:
HTTP/2 200
content-type: text/html; charset=utf-8
cache-control: max-age=0, must-revalidate
content-encoding: br
<!DOCTYPE html>...
The status code is the summary: 2xx worked, 3xx go elsewhere, 4xx your request was wrong, 5xx the server broke. That last distinction decides who fixes it, and status codes carry enough detail to deserve their own article.
4.9 Step 6: Building the Page
Bytes arrive and the browser starts work immediately, without waiting for the whole document. It parses HTML into a DOM, parses CSS into a CSSOM, combines them to decide what is visible, calculates where everything goes, and paints it.
And while parsing, it finds references to other files - stylesheets, scripts, images, fonts - and starts the whole journey again for each one. Section 6 is about this half, which is where most of the time usually goes.
Back to top5. Watching It Happen
Everything above can be observed. The commands in this section were run against petermartin.nl while writing this article, and the output is real, so the numbers are one machine on one connection on one day rather than a benchmark.
5.1 The One Command Worth Memorising
curl can report the elapsed time at each stage, which turns the whole journey into five numbers:
$ curl -sS -o /dev/null -w 'dns: %{time_namelookup}s
connect: %{time_connect}s
tls: %{time_appconnect}s
ttfb: %{time_starttransfer}s
total: %{time_total}s
http: %{http_version} %{http_code}
ip: %{remote_ip}\n' https://petermartin.nl/
dns: 0.023943s
connect: 0.037806s
tls: 0.075484s
ttfb: 0.106997s
total: 0.107034s
http: 2 301
ip: 23.88.98.40
Those are cumulative timestamps, not durations, so the useful numbers are the gaps between them:
DNS lookup 23.9 ms 0 → 23.9
TCP handshake 13.9 ms 23.9 → 37.8
TLS handshake 37.7 ms 37.8 → 75.5
server + first byte 31.5 ms 75.5 → 107.0
─────────
total 107.0 ms and the page has not started yet
Read that table and the priorities become obvious. The TLS handshake was the single most expensive step, at more than a third of the total, and the server's own work was less than a third. Somebody optimising database queries here would be attacking the smallest number on the list.
That last row deserves one correction, because it is the row people quote. It is not server time. It is the request travelling to the server, the server working, and the first byte travelling back - a full round trip with the server's thinking somewhere inside it.
You can separate the two, because the TCP handshake already measured a round trip for you. It is one exchange out and back, so it is a fair estimate of the network cost of any other exchange to the same host:
fourth gap 31.5 ms request + server + response
minus the TCP handshake -13.9 ms one round trip, already measured
─────────
the server's actual work ~17.6 ms
That sharpens the conclusion rather than softening it. The TLS handshake did not merely cost more than the server: at 37.7 ms against roughly 17.6 ms it cost more than twice as much.
One variable makes the arithmetic explicit. time_pretransfer marks the moment curl has finished the handshake and is about to send the request, so adding it isolates the request-send step:
$ curl -sS -o /dev/null -w 'dns: %{time_namelookup}s
connect: %{time_connect}s
tls: %{time_appconnect}s
pre: %{time_pretransfer}s
ttfb: %{time_starttransfer}s
total: %{time_total}s\n' https://petermartin.nl/
dns: 0.001507s
connect: 0.016949s
tls: 0.064378s
pre: 0.064538s
ttfb: 0.101396s
total: 0.101483s
The new line sits 0.16 ms after tls. Across repeated runs that gap stayed consistently under 0.3 milliseconds: writing a request to an already-open, already-encrypted socket costs nothing worth measuring. That is why the shorter command leaves the variable out, and why subtracting the round trip from the fourth gap is a sound estimate of server time rather than a fudge.
One difference from the run in section 5.1 is worth noticing rather than explaining away. DNS took 1.5 ms here instead of 23.9 ms, because by this point the name was already in the cache from the earlier commands. This is section 4.2 happening in front of you, and it is the reason a measurement taken on your fourth attempt flatters the site.
Two warnings before you rely on it. The round trip you subtract is an estimate: TCP and TLS packets can take different paths and a handshake can be delayed by things a later request is not. And it breaks entirely on HTTP/3, where there is no separate TCP handshake to measure, and on a reused connection, where there is no handshake at all.
Now look at the status: 301. The whole 107 ms bought a redirect. Following it costs the rest:
$ curl -sS -o /dev/null -L -w 'final: %{url_effective}
http: %{http_version} %{http_code}
redirects: %{num_redirects}
total: %{time_total}s\n' https://petermartin.nl/
final: https://petermartin.nl/en/
http: 2 200
redirects: 1
total: 0.265913s
266 ms to get a page, of which 107 ms was spent learning that the page is somewhere else. That is the real cost of a redirect, and it is why redirect chains matter: each hop pays for a request and a response before anything useful happens.
Chains are hard to notice one URL at a time, which is why I look for them with a crawler over the whole site rather than by testing pages I already suspect. Every hop in a chain works correctly on its own, so nothing ever appears broken; they accumulate quietly over years of small structural changes, each one adding a hop to links that were fine before. The pattern to look for is not a slow page but an old internal link that now takes three hops to arrive.
5.2 Watching DNS Delegate
dig +trace refuses to use a resolver's cached answer and walks the chain itself, so you can watch the delegation described in section 4.3:
$ dig -4 +trace +noall +answer petermartin.nl
;; Received 239 bytes from 127.0.0.53#53(127.0.0.53)
;; Received 596 bytes from 170.247.170.2#53(b.root-servers.net)
;; Received 303 bytes from 194.0.25.24#53(ns3.dns.nl)
petermartin.nl. 300 IN A 23.88.98.40
petermartin.nl. 300 IN RRSIG A 13 2 300 ...
;; Received 169 bytes from 172.64.35.58#53(finley.ns.cloudflare.com)
Three hops, exactly as described: a root server, then the .nl registry, then the authoritative nameservers - which in this case are Cloudflare's, telling you where the domain's DNS is hosted without anyone having to say so.
Two details in that output are worth naming. The 300 is the TTL in seconds: how long any resolver may cache this answer, and therefore how long a change to it takes to be seen everywhere. Section 7.3 returns to that. The RRSIG line is a DNSSEC signature, meaning this answer is cryptographically signed and a resolver that checks signatures can detect a forged reply.
Run the command twice and the server names will not match the ones above. That is expected, and it is worth understanding rather than ignoring, because it is the shape of the whole system in one observation.
At each level there is a set of servers, and one gets picked:
root 13 names, a.root-servers.net through m.root-servers.net
.nl 3 names, ns1 / ns3 / ns4.dns.nl
petermartin.nl
2 names, chelsea and finley.ns.cloudflare.com
six consecutive runs picked:
e → ns3 → chelsea k → ns3 → chelsea
f → ns1 → finley m → ns4 → chelsea
e → ns3 → chelsea l → ns1 → finley
Your configured resolver is used for one thing only, the first line of that output: telling dig who the root servers are. After that dig makes its own queries and follows the referrals itself, choosing a server at each step from a mix of measured response times and deliberate randomness. Fast servers are favoured, but never exclusively.
Even the letter does not identify a machine. b.root-servers.net is a single address, 170.247.170.2, announced from many separate locations around the world by anycast - "making a particular Service Address available in multiple, discrete, autonomous locations, such that datagrams sent are routed to one of several available locations", as RFC 7094 puts it. Whichever instance is nearest in routing terms answers you.
So there is no such thing as the B root server. There are thirteen root names backed by well over a thousand physical instances, which is how a system that sounds absurdly small has absorbed thirty years of growth, and why a root query is fast from almost anywhere.
This also underpins section 7.3. When servers are chosen per query and every answer is cached independently on its own countdown, there is no coordinated wave for anything to propagate across.
5.3 Watching the TLS Handshake
curl -v narrates the handshake message by message:
$ curl -sS -o /dev/null -v https://www.petermartin.nl/ 2>&1 | grep TLS
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Client hello (1): <-- again?
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
Two things are visible here that no diagram would show you.
ALPN is where the HTTP version is decided. The client offers h2 and http/1.1 inside the TLS handshake, and the server picks. That is why the earlier request reported HTTP/2 without any extra negotiation round trip: it was settled during encryption setup.
There are two Client Hellos. That is a HelloRetryRequest: the client guessed which key exchange group the server would want, guessed wrong, and the server asked it to try again. It costs an extra round trip, it is invisible in every dashboard, and it is one concrete reason the TLS number in section 5.1 was the largest on the list. Section 7.5 comes back to it.
5.4 Reading the Response Head
curl -I asks for headers only, which is the fastest way to see what a server is really doing with a URL:
$ curl -sSI https://petermartin.nl/
HTTP/2 301
server: nginx-rc
date: Thu, 27 Aug 2026 10:20:09 GMT
content-type: text/html; charset=utf-8
location: https://petermartin.nl/en/
set-cookie: e4edc00e04a600eeeaa5f4584a67449a=...; path=/; HttpOnly
Four facts in six lines: the connection is HTTP/2, the answer is a permanent redirect, location names the destination, and the server is nginx. There is also a session cookie being set - on a redirect, on a response no human will ever see. Section 7.6 explains why that is worth noticing.
5.5 A Diagnostic Order That Works
Put together, the tools give you a sequence that finds the broken stage in about a minute:
1. dig +short DOMAIN does the name resolve at all?
no answer → DNS. Stop here.
2. curl -sS -o /dev/null -w '...' URL
which gap is large?
connect never completes → TCP/firewall
tls never completes → certificate
ttfb is the big gap → the server
3. curl -sSI URL what status? redirect chain?
4xx → your request 5xx → their server
4. browser devtools only now, for rendering and subresources
The order is the point: each step assumes the previous one succeeded, so you never debug a stage that has already proven itself.
Answering support questions on the Joomla forum taught me to run this before reading the rest of the report, not after. The description of a problem almost always names a stage, and that part is the least reliable thing in it: "the server is down" arrives for expired certificates, "DNS is broken" arrives for pages that render blank, and both reporters watched something real and then guessed at the cause. Two commands settle it before anyone has to be contradicted.
Back to top6. The Half Nobody Watches
Everything so far got one file to the browser, and in section 5.1 that took 266 ms. On a real page the remaining work is usually larger than everything before it, and almost nobody measures it.
6.1 From Bytes to Pixels
The browser does not wait for the whole document. It builds two structures as bytes arrive and combines them:
HTML bytes → parse → DOM (what is on the page)
CSS bytes → parse → CSSOM (how it should look)
│
combine ├→ render tree (visible things only)
│
layout ├→ where is everything, at what size
│
paint ├→ fill in pixels, in layers
│
composite └→ assemble layers, put on screen
Two of these stages have names worth knowing because they are what performance work actually targets. Layout recalculates positions and sizes, and it is expensive because moving one element can move everything after it. Composite just reassembles already-painted layers, and it is cheap. An animation that only composites is smooth; an animation that forces layout on every frame is not, and that single distinction explains most of the advice about animating transform rather than top.
6.2 Why a Stylesheet Stops Everything
CSS is render-blocking: the browser will not paint until it has the CSS, because painting first would mean showing unstyled content and then rearranging it. So a slow stylesheet delays the page appearing even when the HTML arrived instantly.
Scripts are worse by default. A plain <script> in the head stops the parser: the browser must fetch it, run it, and only then continue reading HTML, because the script might write into the document. Two attributes fix it:
| Form | Behaviour |
|---|---|
<script src> |
Parsing stops while it downloads and runs |
<script src defer> |
Downloads alongside parsing, runs after the document is parsed, in order |
<script src async> |
Downloads alongside parsing, runs the moment it arrives, order not guaranteed |
The browser fights back with a preload scanner: a lightweight second pass that skims ahead through the raw HTML looking for things to download, even while the main parser is blocked. It is why pages are not as slow as the blocking rules suggest, and it is also why resources referenced only from inside JavaScript are found late - the scanner cannot see them, because it does not run code.
6.3 The Connection Was Probably Already Open
Section 4.2 listed the connection pool as a cache, and on a real page it is the one doing the most work. The first request pays for DNS, TCP and TLS. Every subsequent request to the same host reuses that connection and pays none of it.
This is why a page pulling assets from five different domains can be slower than one pulling twice as much from a single domain: each new host means a fresh DNS lookup, a fresh handshake, and a fresh TLS negotiation, all before its first byte. Measured against section 5.1's numbers, each additional host costs roughly 75 ms before it delivers anything.
Two hints let you start that work early, and they are worth knowing because they are the rare optimisation with no downside when used sparingly:
<link rel="dns-prefetch" href="https://fonts.example.com">
<link rel="preconnect" href="https://fonts.example.com">
dns-prefetch resolves the name in advance. preconnect goes further and completes the TCP and TLS handshakes too, so the connection is waiting when it is needed. Use it for hosts you know the page will need; used on everything it wastes connections and battery.
6.4 HTTP/2 and HTTP/3 Change the Arithmetic
Under HTTP/1.1 a connection carried one request at a time, so browsers opened around six per host and developers combined files to reduce request counts.
HTTP/2 made one connection carry many requests simultaneously, which quietly invalidated a decade of advice: concatenating files and building image sprites now often hurts, because one large bundle cannot be cached or invalidated in pieces.
HTTP/3 changes the layer below. It runs on QUIC over UDP rather than TCP, which means the TCP handshake in section 4.6 does not happen at all, and connection setup merges with encryption setup. It also fixes a subtler problem: under HTTP/2 a single lost packet stalls every stream on the connection, because TCP insists on delivering bytes in order. QUIC keeps streams independent, so one lost packet delays one response.
These three versions and their trade-offs are a subject of their own; what matters here is that the shape of the journey is not fixed, and two of its ten steps are optional on a modern connection.
Back to top7. Something Most Users Do Not Know
7.1 The Fragment Never Leaves the Browser
Everything after # in a URL is not sent to the server. Ever. RFC 3986 is explicit: "the fragment identifier is separated from the rest of the URI prior to a dereference, and thus the identifying information within the fragment itself is dereferenced solely by the user agent".
you request https://example.com/guide?v=2#installation
server sees GET /guide?v=2
└─ the query IS sent
#installation └─ the fragment is NOT
Several practical consequences follow, and each one surprises somebody every year:
- Server logs cannot show fragments, so no server-side analytics can tell you which section of a page people jumped to.
- Redirects cannot inspect them. A rewrite rule matching on
#can never fire. - They survive redirects anyway. The browser re-applies the fragment to whatever URL it lands on, which is occasionally useful and occasionally baffling.
- They are a bad place for secrets in one direction and a deliberate one in another: fragments stay out of server logs, which is exactly why some authentication flows return tokens there.
7.2 Your HTTP-to-HTTPS Redirect Often Never Runs
Most sites redirect http:// to https:// and consider the job done. For a large share of visitors that redirect is never requested, because of HSTS.
When a site sends a Strict-Transport-Security header, the browser records that this host is HTTPS-only for the stated duration. On every later visit the browser rewrites http:// to https:// itself, before any network activity. There is no insecure request to intercept, because there is no request.
It goes further. Browsers ship a preload list of domains hardcoded as HTTPS-only, compiled into the browser binary. For a domain on that list, the very first visit by a user who has never seen the site is already protected.
WITHOUT HSTS http:// → request → 301 → https:// request
one insecure request happens, and can be intercepted
WITH HSTS http:// → browser rewrites internally → https://
no insecure request exists
PRELOADED protected on the FIRST EVER visit, before any header
has been received from the site at all
The catch is the one people discover too late: preloading is difficult to undo. Removal requests are processed slowly, and until they take effect in a released browser version, a domain that can no longer serve HTTPS is unreachable rather than degraded. Preloading is a commitment, not a setting.
7.3 "DNS Propagation" Is Not a Thing
Everyone says it, and the mental model it creates is wrong in a way that causes real mistakes. Nothing propagates. No update travels outward across the internet.
What actually happens is expiry. Every DNS answer carries a TTL - the 300 in section 5.2 - and resolvers keep the answer for that long. When it expires, the next question goes back to the authoritative server and gets the new answer. There is no wave. There are millions of independent countdowns, each started when that particular resolver last asked.
The correct model changes what you do:
- The delay is bounded by the TTL you published before the change, not by any global process. Lower the TTL a day ahead of a migration and the switch takes minutes; lower it at the same time as the change and you have gained nothing, because the old TTL is already out there.
- "It has propagated for me" means only that your resolver's copy expired. Somebody else's has not.
- Checking with
dig @8.8.8.8tells you about one resolver, not about the world.
7.4 One Page Is Not One Journey
The whole article so far describes fetching a single file. A modern page might make sixty to a hundred requests, to perhaps eight or ten different hosts, and each new host repeats DNS, TCP and TLS.
This is why the waterfall in developer tools is shaped the way it is, and why the single most effective performance change is often not making anything faster but removing a host. Self-hosting a font that came from a third-party domain does not just save a download; it deletes an entire DNS-plus-handshake sequence from the critical path.
7.5 The Handshake Can Silently Cost an Extra Round Trip
Section 5.3 caught a real example. TLS 1.3 is designed to complete in one round trip, and it manages that by having the client guess which key exchange group the server will choose and send its key share immediately.
When the guess is wrong, the server replies with a HelloRetryRequest, and the client starts again with the right group. The handshake still succeeds, nothing is logged as an error, and the connection quietly cost twice the round trips it was supposed to.
guess right ClientHello → ServerHello + Finished 1 round trip
guess wrong ClientHello → HelloRetryRequest
ClientHello → ServerHello + Finished 2 round trips
On a local connection this is a few milliseconds. On a mobile connection with 80 ms of latency it is 80 ms added to every new connection, invisible to every monitoring tool that measures only whether TLS succeeded. It is the kind of thing you find only by looking at a handshake directly, which takes one curl -v.
7.6 A Cookie Can Be Set on a Page Nobody Sees
The redirect in section 5.4 set a session cookie. The visitor never sees that response - the browser follows the location immediately - but the cookie is stored all the same.
This matters for two reasons that catch people out. It means a session can begin before any page is delivered, so an application may be creating server-side session storage for visitors and crawlers that never reach a real page. And it means cookie consent logic that runs in the rendered page is already too late: the cookie was set by an HTTP header on a response that never rendered, and no amount of JavaScript on the destination page was consulted.
If you audit a site for cookies by loading pages and reading what appears, you will miss every cookie set this way. Reading the headers on the redirect itself is the only way to see them.
7.7 First Byte Is Not First Paint
Time to first byte is the number most server monitoring reports, and it measures the end of step 8 of ten. It says nothing about whether anything appeared on screen.
TTFB the first byte of the HTML arrived
└→ measures DNS + TCP + TLS + server. Nothing else.
FCP the first pixel of content was painted
└→ adds CSS, blocking scripts, fonts, layout, paint
LCP the largest content element finished painting
└→ usually the number a visitor would call "loaded"
A site can have an excellent TTFB and feel broken, if a render-blocking stylesheet on a third host takes a second to arrive. It can also have a mediocre TTFB and feel instant. Reporting only TTFB measures the half of the journey the server controls, which is convenient for whoever runs the server and not the same thing as the visitor's experience.
7.8 HTTPS Hides Less Than People Think
"The connection is encrypted" is true and is narrower than it sounds. Encryption protects the content of the HTTP exchange. Several things around it stay visible to anyone watching the network.
VISIBLE even on HTTPS
the IP addresses routers cannot deliver a packet without them
the hostname, twice once in the DNS query, once in the TLS SNI
the timing and the sizes how much, how often, in what pattern
ENCRYPTED
the path and query /account/orders?id=42
the headers and cookies
the request and response bodies
The hostname leaking twice is the part that surprises people. The DNS lookup asks for a name in plain text unless the resolver uses DNS over HTTPS or DNS over TLS, and section 4.7's SNI names the host again inside the handshake, before encryption starts.
Both leaks now have fixes, and both need more than a browser setting. Encrypted DNS moves the first one to an encrypted channel, though it moves the knowledge to the resolver operator rather than removing it. Encrypted Client Hello, published as RFC 9849 in March 2026, encrypts the SNI, but it needs the client, the server and a DNS record to cooperate, so it is a deployment rather than a checkbox.
The honest summary is the one to give a client: HTTPS reliably hides what you did on a site. It does not reliably hide which site you visited.
7.9 Latency Beats Bandwidth, and It Is Not Close
"The site is slow, we should upgrade the connection" is one of the most expensive wrong answers in this field.
Look again at the measurement in section 5.1. Those 107 milliseconds delivered a 301 - a response of a few hundred bytes. Bandwidth was never a factor. Almost the entire budget went on waiting for round trips: one for DNS, one for TCP, one or two for TLS, one for the request and response.
a round trip costs the same whether you send 100 bytes or 100,000
→ a faster connection makes big files arrive sooner
→ it does NOT make a handshake finish sooner
so the wins that matter are the ones that REMOVE round trips:
connection reuse no handshake at all
fewer hosts no extra DNS + TCP + TLS per domain
a CDN shorter distance, so shorter round trips
TLS session resumption zero round trips instead of one
HTTP/3 merges transport and encryption setup
no redirect one entire request and response deleted
Not one of those is about capacity. This is why a site can feel slow on fast office broadband and fine on a phone, and why doubling a server's bandwidth so often changes nothing a visitor notices.
7.10 Knowing Where This Article Stops
- It stops at one request. Real sessions involve cookies, caches warming, service workers answering from local storage, and a browser that has seen your site before.
- It stops at the happy path plus the obvious failures. Captive portals, corporate proxies that terminate TLS, and DNS-level filtering all rewrite the journey in ways that look like faults in your site.
- It stops at the edges of each subject. DNS, TLS certificates, HTTP status codes, caching and the rendering pipeline each have far more to them than one section can carry, and each has its own article in this section.
- It stops at the browser. A crawler or an AI assistant fetching the same URL may run no JavaScript, accept no cookies, and never reach step 9 at all.
8. Best Practices
- Name the stage before you look for a cause. A blank page proves DNS, TCP and TLS all worked. A certificate warning proves DNS and TCP worked. Half of debugging is refusing to investigate stages that have already succeeded.
- Learn the one curl command in section 5.1. Five numbers tell you which of five stages is slow, and it takes a second to run.
- Read the difference between refused and timed out. Refused means something answered and declined; timed out means silence. They point at different causes and different people.
- Lower DNS TTLs a day before a migration, never during one. The delay you experience is governed by the TTL you published before the change.
- Optimise round trips, not bandwidth. A handshake costs the same at any connection speed. Removing a redirect, a host or a handshake beats any capacity upgrade.
- When one person cannot reach a site that works for everyone else, check both address families. An
Arecord that works and anAAAAthat does not is a classic, and Happy Eyeballs hides it from everybody else. - Reach for
traceroutebefore blaming the server. Routing is the one stage that reports nothing to you and looks exactly like an outage. - Tell clients HTTPS hides what they did, not where they went. The hostname leaks in the DNS query and again in the TLS SNI.
- Count hosts, not just bytes. Each additional domain on a page costs a full DNS and handshake sequence before its first byte. Removing a host often beats optimising what it serves.
- Use
deferfor scripts by default, andasynconly when order genuinely does not matter. A plain blocking script in the head stops the parser. - Treat redirects as a cost. Section 5.1 measured 107 ms to learn that a page was somewhere else. Link to final URLs, and keep chains to one hop.
- Do not report TTFB alone. It measures the stages the server controls, which is not what the visitor experienced.
- Treat HSTS preloading as permanent. Enable it deliberately, and only once you are confident every subdomain can serve HTTPS indefinitely.
- Audit cookies from the headers, not the page. Cookies set on redirects never appear in a rendered document.
- Go to the source. The current specifications are RFC 3986 for URIs, RFC 9110 for HTTP semantics, and RFC 8446 for TLS 1.3. They are more readable than their reputation suggests, and they settle arguments. Check the header of any RFC before quoting it, as section 3 explains: an obsoleted document reads exactly like a current one. This article was checked against them in August 2026.
9. Common Mistakes
9.1 Ten Myths
| Myth | Reality |
|---|---|
| "DNS changes need 24 to 48 hours to propagate" | Nothing propagates. Cached answers expire after the TTL you published before the change. The 24-48 hour figure is folklore from an era of much longer default TTLs. |
| "The site is down" | Six different stages produce that impression, and they need six different fixes. It is a symptom, not a diagnosis. |
| "The fragment is part of the request" | It is never sent. Server logs, rewrite rules and server-side analytics cannot see anything after #. |
| "My HTTP to HTTPS redirect protects everyone" | For visitors with HSTS stored, the redirect is never requested; the browser rewrites the URL itself. For everyone else the first request still travels in plain text. |
| "HTTPS proves the site is trustworthy" | It proves the connection is encrypted and the server controls that hostname. It says nothing about who owns it or whether they are honest. |
| "Combine all the CSS and JavaScript into one file" | Advice from HTTP/1.1, when connections carried one request at a time. Under HTTP/2 and HTTP/3 one large bundle mostly costs you caching granularity. |
| "Good TTFB means a fast site" | TTFB ends at the first byte of HTML. Everything a visitor actually sees happens afterwards. |
| "It works for me, so it works" | Your machine is answering from an HSTS entry, a DNS cache, an open connection and an HTTP cache that the other person does not have. |
| "A slow page means a slow server" | In the measurement in section 5.1 the server accounted for at most 31 ms of 107 ms, and nearer 18 ms once the round trip is subtracted. TLS alone cost twice that. |
| "A faster internet connection makes a slow site fast" | The 107 ms measured in section 5.1 delivered a few hundred bytes. It was spent waiting for round trips, and a round trip costs the same at any bandwidth. |
| "HTTPS means nobody can see which sites I visit" | The IP addresses are visible by necessity, and the hostname appears twice in clear text: in the DNS query and in the TLS SNI. Encrypted DNS and Encrypted Client Hello (RFC 9849, March 2026) address these, and both need deployment beyond the browser. |
| "It is in an RFC, so that is the rule" | RFCs are never revised, only replaced. The document you found may have been obsoleted years ago and reads identically either way. RFC 2026 still describes three standards-track maturity levels; RFC 6410 cut them to two in 2011 and says so only in its own header. |
| "The browser always asks the server" | A service worker can answer from local storage with no network at all, and a CDN edge can answer without the origin ever hearing about it. |
| "HTTP/3 is just HTTP/2 with a new number" | It replaces TCP with QUIC over UDP. The TCP handshake stops existing, and one lost packet no longer stalls every other response on the connection. |
9.2 Other Traps to Avoid
- Restarting the web server when DNS is the problem. If the name does not resolve, nothing ever reached the web server.
- Testing a fix in the same browser that cached the problem. Four caches stand between you and the truth; use a private window or another device.
- Changing DNS and the TTL in the same operation, then being surprised the old value persists.
- Adding
preconnectto every third-party domain. Each one holds open a connection that may never be used. - Assuming a certificate covers subdomains. A certificate for
example.comdoes not coverwww.example.comunless it says so. - Reading a redirect chain as one redirect. Each hop is a full request and response; three hops can cost more than the page.
- Blaming the network for a blank page. A blank page arrived, which means the network worked perfectly.
- Breaking a
-wformat string across lines with a trailing backslash inside single quotes. Single quotes are literal, so the shell does not treat\plus newline as a continuation. curl receives the backslash and prints a stray\between every line. Put real newlines inside the quotes instead, or continue the line outside them. - Testing only over IPv4 when the hostname also has an
AAAArecord, then being surprised that some visitors cannot connect. - Deploying to an origin behind a CDN and declaring it live. The edge may keep serving the old copy until its cache expires or you purge it.
- Trusting a single
digagainst one public resolver as evidence of what everybody sees. - Forgetting that the browser upgrades bare hostnames to HTTPS, then wondering why a plain HTTP test never reaches the server.
10. Summary
- Entering a URL starts nine to eleven independent operations, not one. Each has its own cache, its own timeout and its own way of failing.
- The name is a fossil: Universal Resource Identifiers in June 1994 became Uniform Resource Locators by December 1994. A locator says where something is, which is why links die.
- Four caches can end the journey before any network activity: HSTS, the HTTP cache, the DNS cache and the connection pool. This is what "it works for me" usually means.
- DNS is delegation, not a directory: root, then TLD, then authoritative. Nobody holds a list of every domain.
- Refused and timed out are different diagnoses. Refused means something answered; timed out means silence.
- In a real measurement, the TLS handshake cost more than twice what the server did - 37.7 ms against roughly 17.6 ms once the round trip is subtracted - and the whole 107 ms bought only a redirect.
- An IP address is not a route. The packet goes to your gateway, gets its private source address rewritten by NAT, and is then forwarded hop by hop by routers that each know only the next step.
- A hostname can have two address families, and Happy Eyeballs races them. It is why almost nobody discovers their IPv6 is broken.
- SNI names the host inside the TLS handshake, before encryption exists, because the server must pick a certificate before it can read the
Hostheader. It is what makes HTTPS on shared hosting possible, and it leaks the hostname. - HTTPS hides what you did, not where you went. The hostname travels in clear text twice, in the DNS query and in the SNI.
- Latency beats bandwidth. A round trip costs the same for 100 bytes as for 100,000, so the wins that matter remove round trips rather than adding capacity.
- The fragment is never sent to the server, so no server log and no rewrite rule will ever see it.
- "DNS propagation" describes nothing that happens. Cached answers expire on independent countdowns set by the TTL published before the change.
- Every extra host on a page costs a full DNS and handshake sequence. Removing a domain often beats optimising it.
- TTFB measures the half of the journey the server controls. First paint happens later, after CSS, blocking scripts and layout.
- Two of the ten steps are already optional: HTTP/3 removes the TCP handshake, and session resumption can remove the TLS round trip.
THE JOURNEY (name the stage before hunting a cause)
1 parse URL address or search? scheme and port filled in
2 caches HSTS / HTTP cache / DNS cache / connection pool
└→ any hit ends the journey early
3 DNS resolver → root → TLD → authoritative
4 routing route table → gateway → NAT → hop by hop (BGP)
ip route get IP shows gateway + source address
A vs AAAA raced by Happy Eyeballs (RFC 8305)
5 TCP SYN, SYN-ACK, ACK (HTTP/3: skipped)
6 TLS cipher + certificate + keys (1 RTT on TLS 1.3)
SNI names the host IN THE CLEAR, before encryption
7 request GET /path + Host header
8 server CDN edge? proxy? origin? - often not the origin
9 response status, headers, body
10 render DOM + CSSOM → layout → paint → composite
11 repeat for every subresource, to every host
WHAT THE SYMPTOM RULES OUT
blank page DNS + TCP + TLS all SUCCEEDED
cert warning DNS + TCP succeeded
"cannot find" DNS failed; the server was never contacted
refused something answered and said no → service down
timed out silence → firewall / wrong IP
THE ONE COMMAND
curl -sS -o /dev/null -w 'dns: %{time_namelookup}\nconnect: %{time_connect}
tls: %{time_appconnect}\nttfb: %{time_starttransfer}\ntotal: %{time_total}
http: %{http_version} %{http_code}\n' URL
cumulative timestamps: read the GAPS, not the values
A REAL MEASUREMENT petermartin.nl, August 2026
DNS 23.9 ms
TCP 13.9 ms ├→ also your round-trip estimate
TLS 37.7 ms ├→ the LARGEST single cost
4th gap 31.5 ms NOT server time: request + server + response
server ~17.6 ms subtract the TCP handshake (one round trip)
total 107.0 ms ... and the answer was a 301
follow it 266.0 ms total, to reach an actual page
add %{time_pretransfer} to split out the request-send step
(measured 0.14-0.27 ms here: not worth its own line)
estimate FAILS on HTTP/3 (no TCP handshake) and on reused
connections (no handshake at all)
DIAGNOSTIC ORDER (each step assumes the last one passed)
1 dig +short DOMAIN resolves? no → DNS, stop
2 curl -w timings which gap? → TCP / TLS / server
3 curl -sSI URL status? chain? 4xx yours, 5xx theirs
4 devtools only now: rendering and subresources
DNS TTL
dig +trace shows the delegation and the TTL
server names VARY per run: 13 root names, 1 set per level, chosen
by response time plus randomness; and each name is anycast, so
the letter does not identify a machine either
nothing "propagates": every resolver runs its own countdown
lower the TTL a DAY BEFORE a migration, never during
WHAT HTTPS DOES NOT HIDE
visible IP addresses; the hostname TWICE (DNS query + TLS SNI);
timing and sizes
encrypted path, query, headers, cookies, bodies
fixes DNS over HTTPS / TLS, and Encrypted Client Hello
(RFC 9849, March 2026) - both need deployment, not a checkbox
say this HTTPS hides WHAT you did, not WHICH SITE you visited
LATENCY, NOT BANDWIDTH
a round trip costs the same for 100 bytes as for 100,000
remove round trips: connection reuse, fewer hosts, a CDN,
session resumption, HTTP/3, no redirect
capacity upgrades do not shorten a handshake
REVALIDATION (a request with no body)
ETag: "abc123" → If-None-Match: "abc123" → 304 Not Modified
still one round trip, but no content transferred
FRAGMENTS
https://example.com/guide?v=2#installation
server sees GET /guide?v=2 the query IS sent
#installation NEVER sent, to anyone, ever
RENDER BLOCKING
CSS blocks painting
<script src> blocks parsing
<script src defer> parses alongside, runs after parse, in order
<script src async> parses alongside, runs on arrival, any order
preload scanner skims ahead for downloads; cannot see inside JS
CONNECTION HINTS (use sparingly, on hosts you know you need)
<link rel="dns-prefetch" href="/..."> resolve the name early
<link rel="preconnect" href="/..."> DNS + TCP + TLS early
THE THREE TIMINGS PEOPLE CONFUSE
TTFB first byte of HTML = DNS + TCP + TLS + server
FCP first content painted + CSS, blocking JS, fonts, layout
LCP largest element painted ~ what a visitor calls "loaded"
READING AN RFC (Request for COMMENTS, not change)
free at rfc-editor.org, 9000+ documents, no paywall, no legal force
NEVER revised: a change means a NEW number
Obsoletes: N replaces N entirely
Updates: N changes part of N, rest still stands
CHECK THE HEADER before quoting: an obsoleted RFC reads exactly
like a current one, and the date alone tells you nothing
not all are standards: Informational, Experimental, BCP, Historic
... and an April Fools' joke most years
Verified August 2026 against RFC 3986 (URIs), RFC 9110 (HTTP semantics),
RFC 9113 (HTTP/2), RFC 9114 (HTTP/3) and RFC 8446 (TLS 1.3)
Learn the stages and the vague complaints stop being vague. A page that will not load is a question with six possible answers, and five minutes with dig and curl eliminates four of them. And when a site is "slow" and everyone is looking at the database, it is worth remembering the measurement above: the server was the third-largest cost in a request that spent its entire budget discovering the page had moved.


Peter is a Joomla specialist and a Linux admin for fast, secure and scalable websites.