What Happens Between Typing a URL and the Page Loading
DNS turns a hostname like crosscheck.app into an IP address. TLS encrypts the connection to that IP before any HTTP bytes flow. Together they cover the first two seconds of every page load — the resolver chain on the DNS side, the handshake on the TLS side. Most developers use both daily and couldn't sketch either on a whiteboard. This guide walks the whole journey end to end, with dig and openssl s_client examples and the error codes you've definitely seen.
Key takeaways
- DNS resolution walks a chain — browser cache, OS cache, recursive resolver, root, TLD, authoritative — each layer with its own TTL.
- TLS 1.3 supports roughly 73–75% of top sites as of mid-2025 (SSL Pulse, June 2025); TLS 1.2 still serves the long tail of legacy clients.
- TLS 1.3 is 1-RTT by default and 0-RTT for resumed sessions, against 2-RTT for TLS 1.2.
- Most "site won't load" errors map to one layer — DNS (
ERR_NAME_NOT_RESOLVED) or TLS (ERR_CERT_*). - DoH is default-on in Chrome, Firefox, and Edge. ECH (RFC 9849, March 2026) closes the last SNI leak.
The full journey, in 12 steps
Press Enter and the browser sets off a chain that, on a good day, finishes in 200–500 ms:
- Parse the URL into scheme, host, port, path.
- Check the HSTS list — if the host is on it, upgrade
http://tohttps://immediately. - Look up the IP — browser cache → OS resolver cache → configured DNS resolver → recursive walk.
- Open a TCP connection on port 443 (or QUIC over UDP for HTTP/3).
- TLS ClientHello — versions and ciphers the client supports.
- TLS ServerHello — server picks a cipher, sends its certificate chain.
- Client verifies the chain against its trusted root store.
- Both sides derive session keys; encrypted application data starts flowing.
- HTTP request → response → render. Every cross-origin asset triggers more DNS + TLS rounds.
Steps 3 through 8 are DNS and TLS — and they're where most "the site is slow" or "the site won't load" investigations actually live.
DNS: how a name becomes an IP
DNS is a hierarchical, distributed database. The mental model is a tree. The root zone (.) sits at the top. Below it sit the TLDs — .com, .app, .io, .co.uk. Below each TLD sit the authoritative nameservers for individual domains, which hold the actual records (A, AAAA, CNAME, MX, TXT, NS) that map hostnames to IPs and other metadata.
The browser does not walk this tree itself. It hands the question to a recursive resolver — typically your ISP's resolver, or a public one like 1.1.1.1 (Cloudflare), 8.8.8.8 (Google), or 9.9.9.9 (Quad9). The recursive resolver does the walking.
Recursive vs authoritative resolvers
| Resolver type | What it does |
|---|---|
| Recursive resolver | Receives a query, walks the tree on your behalf, caches the answer (Cloudflare 1.1.1.1, Google 8.8.8.8, your ISP) |
| Root nameserver | Tells the recursive resolver which TLD nameserver to ask |
| TLD nameserver | Owns each TLD — Verisign for .com, Public Interest Registry for .org |
| Authoritative nameserver | Holds the actual DNS records for the domain and answers definitively |
A clean lookup of crosscheck.app: recursive resolver asks the root, root says "ask the .app nameserver", .app nameserver says "ask the authoritative nameserver for crosscheck.app", authoritative nameserver returns the A record. The recursive resolver caches that answer for the TTL the authoritative server specified, then hands it back to the OS.
Record types you actually use
| Record | What it points to | Use case |
|---|---|---|
| A / AAAA | IPv4 / IPv6 address | Map crosscheck.app to an IP |
| CNAME | Another hostname | Alias www.crosscheck.app to crosscheck.app |
| MX | Mail server hostname + priority | Route email to Google Workspace, Fastmail, etc. |
| TXT | Arbitrary text | SPF, DKIM, DMARC, ownership verification |
| NS | Authoritative nameservers for the zone | Delegating a subdomain or moving DNS providers |
| HTTPS / SVCB | Service binding with ALPN, port, ECH config | HTTP/3 advertisement, ECH key delivery |
The newer HTTPS record (RFC 9460) is the one to watch — it's how browsers learn about HTTP/3 endpoints and ECH keys in a single DNS lookup, instead of starting a TCP connection and then upgrading.
TTL and caching layers
Every DNS record has a TTL — time-to-live in seconds — that tells every caching layer how long it may reuse the answer. A typical web record sits at 300–3600. Lower it before a migration so you can flip records quickly; raise it back after.
DNS gets cached in at least four places:
- Browser cache — Chrome holds DNS answers for around 60 seconds; check
chrome://net-internals/#dns. - OS resolver cache — macOS
mDNSResponder, Linuxsystemd-resolved, Windows DNS Client. Flush withsudo dscacheutil -flushcache && sudo killall -HUP mDNSResponderon macOS oripconfig /flushdnson Windows. - Router / ISP recursive resolver — caches according to the record's TTL. This is usually the layer that's stale when your DNS change "hasn't propagated yet".
- Authoritative nameserver — the source of truth.
The "DNS propagation can take 48 hours" line is mostly about these caches stacking. The authoritative record updates instantly; downstream resolvers update when their TTLs expire.
Inspecting DNS with dig
dig is the tool. Skip nslookup — it's older and gives you less detail. Basic shape:
dig crosscheck.app
dig @1.1.1.1 crosscheck.app +trace # walk the tree visibly
dig crosscheck.app MX # mail records
dig crosscheck.app TXT # SPF / DKIM / DMARC
dig crosscheck.app HTTPS # HTTPS / SVCB record (ECH, HTTP/3)
dig +short crosscheck.app # just the IP
+trace is the single most useful flag when debugging propagation — it shows root → TLD → authoritative explicitly. If the answer is NXDOMAIN, the domain doesn't exist. If it's SERVFAIL, the authoritative nameserver is broken or unreachable. If dig returns the IP but the browser still says ERR_NAME_NOT_RESOLVED, the browser is probably using DoH against a resolver that disagrees with your shell's resolver — open chrome://net-internals/#dns to confirm.
DNS over HTTPS and DNS over TLS
Traditional DNS sends queries in plaintext over UDP port 53. Your ISP sees every hostname you look up — and so does anyone sniffing your café Wi-Fi. DoH (RFC 8484) wraps DNS queries in HTTPS to a DoH-enabled resolver. DoT (RFC 7858) wraps them in a dedicated TLS connection on port 853. Same goal, different transport.
In practice, DoH is the one developers see. Firefox shipped it on by default in the US in February 2020; Chrome followed in May 2020. As of 2026 both browsers ship DoH-on-by-default in most regions when the configured resolver supports it, and Firefox reports DoH adoption above 85% among US users. Chrome ships five pre-configured providers — Google, Cloudflare, Quad9, NextDNS, CleanBrowsing — and will auto-upgrade if your system resolver matches one.
Three things change for developers:
- DNS queries don't show up in
tcpdumpon port 53 — they show up as HTTPS to your resolver. Expect encrypted HTTPS to1.1.1.1ordns.google, not plaintext UDP. - Corporate split-horizon DNS can break. Browser DoH bypasses the system resolver, which can route internal lookups to a public DoH resolver that doesn't know your internal zones. The fix is usually an MDM policy disabling DoH or a corporate canary domain.
- DoH is a hard prerequisite for ECH — ECH keys are fetched via DNS, and you don't want that record going out in plaintext.
TLS: how the connection becomes private
Once the browser has an IP, it opens a TCP connection on port 443 and starts a TLS handshake. The handshake has two jobs — agree on a cipher suite, and verify the server is who the certificate says it is.
TLS 1.2 was published in 2008 and remains universally supported. TLS 1.3 was finalised in 2018 (RFC 8446) and is now the dominant protocol on the public web — roughly 73% of encrypted traffic and 75.3% of top sites as of June 2025 per Qualys SSL Pulse. TLS 1.2 still hangs around in older enterprise stacks, embedded devices, and middleboxes. All major browsers, CDNs, and load balancers support both.
The TLS 1.3 handshake, step by step
The TLS 1.3 handshake is 1-RTT — one round trip — which is the single biggest reason it's faster than TLS 1.2's 2-RTT handshake.
- ClientHello — client sends supported TLS versions, cipher suites, key exchange groups, the SNI, and — critically for 1.3 — a key share (its public ECDHE key for one or more groups it expects the server to accept).
- ServerHello — server picks a cipher and key exchange group, sends its own key share. From that moment both sides can derive session keys, and the rest of the handshake is encrypted.
- EncryptedExtensions, Certificate, CertificateVerify, Finished — sent in the same flight as ServerHello. The certificate proves the server owns the hostname; CertificateVerify proves the server holds the matching private key; Finished commits both sides to the keys.
- Client Finished — client verifies the chain, sends its own Finished, and application data follows in the same flight.
One round trip — ClientHello out, ServerHello + Finished back. TLS 1.2 needed two because the key exchange parameters weren't in the ClientHello.
0-RTT and session resumption
TLS 1.3 also defines 0-RTT (early data). If the client has a recent session ticket from a previous connection, it can send application data in the very first flight — alongside ClientHello — encrypted under a key derived from the previous session. Page loads from a known origin can feel near-instant.
The catch: 0-RTT data is replayable. An attacker who captured the encrypted early data can replay it; the server has no way to tell. So 0-RTT is safe for idempotent GETs and dangerous for anything that mutates state. CDNs like Cloudflare and Fastly only allow 0-RTT for GETs, and you should too.
TLS 1.2 vs TLS 1.3 at a glance
| Aspect | TLS 1.2 | TLS 1.3 |
|---|---|---|
| Handshake RTT | 2 | 1 (0 with resumption) |
| Cipher suites | Many — including RSA, CBC, RC4 | Five AEAD-only suites |
| Forward secrecy | Optional | Mandatory |
| Encrypted handshake messages | No | Yes (after ServerHello) |
| Adoption among top sites | 100% support | ~75.3% support (June 2025) |
If you're still terminating TLS on infrastructure that doesn't support 1.3, the upgrade is almost always worth it — faster handshakes, smaller attack surface, forward secrecy by default.
Certificates, chains, and the trust store
The certificate the server sends is a signed claim — "this public key belongs to crosscheck.app" — signed by a Certificate Authority (CA). The CA's certificate is itself signed by a higher CA, up to a root certificate that's pre-installed in the OS or browser trust store.
A typical chain has three or four certs:
- Leaf certificate — for
crosscheck.app, valid 90 days (Let's Encrypt) or up to ~13 months for paid CAs. - Intermediate certificate(s) — signed the leaf, owned by the CA.
- Root certificate — the trust anchor, in your local store. The server does not send this; your client already has it.
The client walks the chain: leaf → intermediate → root. If every signature checks out and the root is in the local trust store, the cert is trusted. If anything fails — broken signature, expired cert, missing intermediate, unknown root — the handshake aborts and the browser shows an error.
Three things worth knowing:
- Trust stores are not one shared list. macOS uses Apple's, Windows uses Microsoft's, Mozilla maintains its own — each runs its own CA/Browser Forum compliance process and can distrust a CA unilaterally.
- Let's Encrypt certs last 90 days; automated renewal is non-negotiable.
- If you pin certificates, pin the leaf's public key or the root — never the intermediate.
Inspecting TLS with openssl s_client
The TLS equivalent of dig is openssl s_client. It connects to a host and prints the negotiated parameters and the full certificate chain:
# Full handshake + chain (note: -servername sets the SNI)
openssl s_client -connect crosscheck.app:443 -servername crosscheck.app
# Just the expiry dates
echo | openssl s_client -connect crosscheck.app:443 -servername crosscheck.app 2>/dev/null \
| openssl x509 -noout -dates
# Force a specific TLS version (useful for legacy debugging)
openssl s_client -connect crosscheck.app:443 -servername crosscheck.app -tls1_2
openssl s_client -connect crosscheck.app:443 -servername crosscheck.app -tls1_3
-servername matters — without it, a virtual-hosted server won't know which cert to send. If the server only supports TLS 1.3 and the client forces 1.2, the handshake fails with no protocols available — exactly what you want to see in that test.
Encrypted Client Hello (ECH)
There's one leak left in TLS 1.3 — the ClientHello includes the SNI in plaintext, the hostname the client wants to talk to. A network observer can see crosscheck.app even though they can't see the page contents. For shared-IP hosting (most CDNs), this is the last metadata leak in HTTPS.
ECH closes it. The client encrypts the inner ClientHello — including the SNI — using a public key fetched from the server's DNS HTTPS record. The outer ClientHello shows a generic hostname (typically the CDN's public name). The observer sees the client talking to "the CDN" but not which tenant.
ECH was finalised as RFC 9849 in March 2026 (OpenSSL announcement), alongside RFC 9848 for DNS bootstrapping. Firefox enabled it by default starting in version 119; Chrome is rolling out gradually on stable; Safari supports it on recent Apple OS releases; Edge ships it as policy-controllable. Two prerequisites: DoH (or DoT) must be active, and the server must publish an HTTPS record with an ech= parameter. Check with dig crosscheck.app HTTPS — if you see ech=<base64>, the server is advertising a config.
Common browser errors and what they really mean
Most "can't load the site" errors map to a specific step in the DNS or TLS flow. The big ones:
| Chrome error | Layer | What it means | First thing to check |
|---|---|---|---|
| ERR_NAME_NOT_RESOLVED | DNS | Hostname did not resolve | Typo in URL, dead domain, broken local DNS, DoH disagreement |
| ERR_CONNECTION_REFUSED | TCP | Server refused on the port | Service not running, wrong port, firewall |
| ERR_CONNECTION_TIMED_OUT | TCP | Nothing answered | Wrong IP, packet filter, server down |
| ERR_SSL_PROTOCOL_ERROR | TLS handshake | Negotiation failed before certs | Version mismatch, broken middlebox, no shared cipher |
| ERR_CERT_AUTHORITY_INVALID | TLS chain | Cert signed by an untrusted CA | Self-signed cert, missing intermediate, corporate HTTPS inspection |
| ERR_CERT_DATE_INVALID | TLS dates | Cert expired or device clock wrong | openssl s_client for not-after, then local clock |
| ERR_CERT_COMMON_NAME_INVALID | TLS hostname | Hostname doesn't match cert's SAN list | Cert issued for the wrong domain or missing the SAN |
| ERR_CERT_REVOKED | TLS revocation | CA revoked the cert via CRL or OCSP | Reissue and redeploy |
Two error patterns trip up everyone at some point:
- A cert chain that works in Chrome but fails on iOS or curl — almost always a missing intermediate. Chrome's AIA fetching can pull a missing intermediate from the leaf certificate; many other clients won't. Always serve the full chain.
ERR_CERT_AUTHORITY_INVALIDon a corporate laptop only — your employer's HTTPS-inspecting proxy (Zscaler, Palo Alto, Netskope) is terminating TLS and re-signing with its own CA. That CA is normally installed by MDM; if the install failed, every HTTPS site looks broken.
A debugging cheatsheet
When a site won't load, walk the layers in order:
# 1. Is DNS working?
dig +short example.com
dig @1.1.1.1 example.com +trace
# 2. Can TCP reach the port?
nc -vz example.com 443
# 3. Does TLS complete?
openssl s_client -connect example.com:443 -servername example.com -brief
# 4. Is the cert valid for this name and not expired?
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates
# 5. Does HTTPS round-trip a request?
curl -v https://example.com/
# 6. What does the browser see?
# Open chrome://net-internals/#dns and chrome://net-internals/#sockets
If steps 1–4 pass and the browser still complains, the issue is browser-level — extension interfering, stale HSTS pin, cached DoH answer, certificate transparency requirement. The chrome://net-internals panels are the source of truth there. For the broader bug-isolation flow, the step-by-step debugging guide covers how to identify which layer owns any given bug, and javascript debugging tips for developers handles errors that surface after the page loads.
FAQ
Why is DNS resolution sometimes slow even when my connection is fast?
DNS speed depends on the resolver, not the bandwidth. A cold lookup against a slow ISP resolver can take 100–300 ms; a warm lookup against 1.1.1.1 is usually under 10 ms. Switch resolvers if your default is consistently slow.
Why does my DNS change take so long to propagate?
The authoritative record changes instantly. What takes time is every downstream cache hitting the end of its TTL. Lower your TTL to 60–300 seconds a day before any planned change, then raise it again after.
Is TLS 1.2 still safe to use in 2026?
Yes, if it's configured correctly — TLS 1.2 with AEAD cipher suites (ECDHE + AES-GCM or ChaCha20-Poly1305) and forward secrecy is still considered secure. What's not safe is TLS 1.2 with legacy ciphers like RC4, 3DES, RSA key exchange, or CBC modes.
What's the difference between SSL and TLS?
SSL is the old name; TLS is the current name. SSL 2.0 and 3.0 were retired (SSL 3.0 was killed by POODLE in 2014); the protocol was renamed TLS at version 1.0 in 1999. People still say "SSL certificate" out of habit, but everyone means TLS.
Why does my cert work in Chrome but not in my Node.js script?
Node uses its own bundled CA list, not the system trust store. If you have a corporate CA installed at the OS level, Node won't see it unless you set NODE_EXTRA_CA_CERTS=/path/to/ca.pem. Same pattern with Python (certifi), Go, and curl built against different TLS backends. When the browser trusts a cert and your script doesn't, the trust store is the first place to look.
Where Crosscheck fits
DNS and TLS issues are some of the hardest bugs to file well because they happen at connection time, before the page can run any embedded bug-report widget. When something does load far enough to break, Crosscheck captures the screenshot, the screen recording, the console logs, and the network requests — including failed TLS and CORS errors — and sends a complete report to Jira, Linear, ClickUp, GitHub, or Slack in one step.



