Loading live status…
Loading live status…
The classic interview question, answered for a web that has moved on. HSTS before the first packet, HTTPS records that skip the TCP handshake, QUIC instead of TCP, TLS 1.3 in one round trip — and a map of which stage each kind of outage actually lives in.
Ask this in an interview and you will usually get the same answer: DNS lookup, TCP three-way handshake, TLS handshake, HTTP GET, render.
That answer is a good skeleton and it is now wrong in at least three specific places. The first network packet may not be DNS. The TCP handshake may not happen at all. And the request you typed as http:// may never exist as an HTTP request.
None of that is exotic. It is the default behaviour of a current browser talking to a site behind any major CDN. Here is the whole sequence, in order, with the parts that changed marked as they come.
You typed into the omnibox, which is one box doing two jobs. Before anything resolves, the browser has to decide whether readme is a hostname on your corporate network or a search for the word "readme".
The heuristics are unglamorous and mostly consistent across browsers: a string with a scheme is a URL, a string with a dot and a known-ish TLD is a URL, a string with a space is a search, and a single word is a search unless a previous navigation taught the browser that a host by that name resolves.
This matters more than it sounds. A single-word intranet hostname is ambiguous enough that browsers historically fired a DNS query and a search in parallel, and ISPs that answered every NXDOMAIN with an ad page turned that into a privacy leak. If you have ever watched a browser hesitate for a beat on a bare hostname, this is that.
You typed http://github.com. No HTTP request is made.
Browsers ship a preloaded HSTS list — tens of thousands of domains compiled into the binary — plus a dynamic list built from Strict-Transport-Security headers the browser has seen before. If the host is on either list, the scheme is rewritten to https:// in memory, before a socket exists.
This is a real ordering detail, not a technicality. The old model was: request over HTTP, receive a 301 to HTTPS, follow it. That first cleartext request is exactly where an attacker on the same network strips the redirect and keeps you on HTTP forever. HSTS preloading removes the request entirely, so there is nothing to strip.
It is also why this site sends Strict-Transport-Security without a preload directive. Preloading is a one-way commitment: getting off the list takes months and ships with the browser.
The browser now parses the string. Not by RFC 3986, which is the specification everyone cites, but by the WHATWG URL Standard, which is the one browsers actually implement. They disagree in enough edge cases that "parse the URL" is a real source of security bugs — a proxy validating with an RFC 3986 parser and a browser resolving with a WHATWG one can be made to disagree about which host is being addressed.
The host is normalized to lowercase, and if it contains non-ASCII it is converted to Punycode: münchen.de becomes xn--mnchen-3ya.de. Only ASCII goes on the wire. Browsers then decide, per script and per TLD, whether to display the Unicode form or the Punycode one — the anti-homograph rules that stop a Cyrillic а in аpple.com from rendering as the real thing.
The fragment (#section) is stripped here. It never leaves the machine.
Now a name has to become an address. The browser walks a chain of caches, and each layer can answer:
| Layer | Typical lifetime | Notes |
|---|---|---|
| Browser's own DNS cache | ~60s | Chrome keeps its own, separate from the OS |
| OS resolver cache | Record TTL | ipconfig /displaydns, resolvectl statistics |
hosts file |
Forever | Not a cache; checked before any query |
| Configured resolver | Record TTL | Your ISP, or 1.1.1.1 / 8.8.8.8 |
| Authoritative servers | — | The only source of truth |
Only if every cache misses does a query go out. The resolver then does the recursive walk — root, TLD, authoritative — which is four round trips in the worst case and nearly never happens, because the root and TLD answers are cached almost permanently.
Two things here are newer than the standard answer.
DNS is often encrypted now. DNS-over-HTTPS (RFC 8484) sends the query as an HTTPS request to a resolver, which means your first packet may be a TLS handshake with Cloudflare or Google rather than a UDP datagram to your ISP. Firefox enables it by default in several regions; Chrome upgrades automatically when your existing resolver supports it.
The browser asks for more than an address. Alongside A (IPv4) and AAAA (IPv6), current browsers query for HTTPS records (RFC 9460) — a record type that returns, in one lookup, which protocols the host supports, alternative endpoints, and Encrypted Client Hello keys. Which leads directly to the step everyone still describes wrong.
The standard answer says: open a TCP connection with a three-way handshake.
Frequently, no.
If the HTTPS record advertises alpn="h3", or if the browser has a cached Alt-Svc header (RFC 7838) from a previous visit, it will attempt QUIC (RFC 9000) over UDP instead. QUIC folds the transport handshake and the TLS handshake into one exchange, so the connection is established and encrypted in a single round trip. There is no separate TCP handshake because there is no TCP.
This is not a fringe path. HTTP/3 is served by default by Cloudflare, Vercel, Fastly and CloudFront, and it is what your browser will pick for a large share of the sites you visit.
When TCP is used, the other correction applies: the browser does not pick one address and commit to it. Under Happy Eyeballs v2 (RFC 8305) it starts an IPv6 connection, waits roughly 50ms, starts an IPv4 connection in parallel, and keeps whichever completes first — cancelling the loser. A host with a broken IPv6 path costs you 50 milliseconds instead of a 30-second timeout.
So the honest version of this step is: the browser races several possible connections and uses the first one that works.
Assume TCP won the race. TLS 1.3 (RFC 8446) needs one round trip, not the two that TLS 1.2 needed:
h2, http/1.1).Two fields in there do outsized work.
SNI is how thousands of sites share one IP. It is also, historically, the one part of an HTTPS connection that travels in cleartext — anyone on the path can see which host you asked for even though they cannot read the traffic. Encrypted Client Hello is the fix, and it needs the public key that the HTTPS DNS record from step 4 carries. That is why these two steps are now coupled.
ALPN is where the HTTP version is decided. Not by the URL, not by a header — by a field in the TLS handshake, before a single byte of HTTP exists. A server built against an OpenSSL without ALPN support silently serves HTTP/1.1 forever and logs nothing about it.
The client then validates the chain: signature up to a trusted root, hostname match, not expired, not revoked, and — increasingly — present in Certificate Transparency logs. Any failure here produces an interstitial, and no HTTP request is sent.
After all of that machinery, this is the part that carries your intent:
GET / HTTP/2
:authority: github.com
accept-encoding: gzip, br, zstd
Under HTTP/2 and HTTP/3 it is not even text — it is a binary HEADERS frame with the field names compressed against a shared table, so :method: GET costs about one byte. We wrote about how that framing works separately.
The point worth taking away is proportion. Everything before this step exists to make this step possible; the request itself is trivial.
Your packet arrives at an anycast address, meaning the same IP is announced from hundreds of locations by BGP and routing delivers you to the nearest one. You reached an edge node, and the edge decides:
At the origin, in a modern deployment, "the server" may be a function that does not exist until your request creates it. That cold start is real latency in the response, and it is invisible to every layer above.
Whatever produces the bytes, the reply comes back with a status code, a content-type, cache directives, and the security headers that will govern what the page is permitted to do — CSP, X-Content-Type-Options, Referrer-Policy.
The browser does not wait for the response to finish.
HTML arrives as a stream and is tokenized incrementally into the DOM. Meanwhile a second, lighter parser — the preload scanner — runs ahead of the main one looking only for URLs, so images and stylesheets start downloading while the document is still arriving. This is why a script tag in the middle of your body blocks less than it used to, and why moving a stylesheet link below the fold does not help.
CSS builds the CSSOM in parallel. It is render-blocking by nature: the browser cannot paint text it might have to restyle. The DOM and CSSOM combine into a render tree, which is then:
A synchronous script blocks parsing entirely, because a script may call document.write. defer runs it after parsing; async runs it whenever it lands.
This stage is what the Core Web Vitals measure: LCP for when the main content painted, CLS for whether it moved afterwards, and INP for how fast the page answers an interaction. INP replaced FID in 2024 — it measures the full interaction rather than just the delay before handling started, which made a lot of previously-passing pages fail honestly.
Every stage above has a characteristic failure, and the symptom usually identifies the stage:
| Stage | Failure | What you see |
|---|---|---|
| DNS | Record removed or expired zone | NXDOMAIN, works for others, fails for you |
| DNS | Stale cache after a migration | You reach the old server; a colleague reaches the new one |
| Routing | BGP withdrawal | Timeouts, not errors. Nothing answers at all |
| TLS | Expired certificate | Interstitial warning, site is otherwise fine |
| TLS | Missing intermediate | Works in browsers, fails in curl and every SDK |
| Origin | Overload | 502 / 503, often only past the edge cache |
| Origin | Cold start or slow query | 504, or a page that loads at a crawl |
| Application | Bad deploy | A clean 200 serving a broken page |
That last row is the one that matters most, and it is the one nothing in the network stack can detect. A 200 OK is a statement about the transport, not about whether the product works.
The distribution is worth internalising too: the stages that produce the most user-visible downtime are DNS, routing and certificates — the ones furthest from the application code, and the ones nobody's dashboard is watching. We went through ten of these mechanisms in why major websites go down.
An uptime check is one pass through this chain, which makes it a narrower instrument than it appears.
A checker that reuses a connection is not repeating steps 4 through 6. It is sending an HTTP request through a tunnel it opened earlier, which means an expired certificate, a DNS change or a withdrawn route can all be invisible to it until something forces a reconnect. That is a faster check and a smaller one, and it is fine as long as you know which of the two you built.
It also explains why a provider's own status page and your experience of that provider can honestly disagree. Their check runs from inside their network — past the DNS you resolve, past the routes you take, past the certificate your client validates. It is measuring stage 8 and calling the result "operational".
This is the whole reason isuptime reads 102 providers' feeds from the outside and stamps every answer with when it looked. The gap between "the origin responded" and "you could load the page" is most of the interesting failures, and a green dot is a claim about one stage of a chain with ten of them.
We wrote about that gap in a status page is not a measurement. This post is the other half: the ten stages the measurement is quietly summarising.