You typed romariostani.com and hit Enter. In a fraction of a second, your keyboard, your operating system, your browser, your router, your ISP, a few thousand kilometres of fibre, a handful of routers you will never see and a server somewhere all worked together, and none of them asked you anything.
This page walks through that journey step by step. Each step has a short version and a ▸ go deeper section if you want the details.
Your browser records a timestamp for most of the steps below. These are the real numbers from your visit:
Under every key is a switch sitting on a grid of wires called the key matrix.
A small microcontroller inside the keyboard scans that grid over and over, powering one row at a time
and checking which columns carry current. When you press r, a circuit closes at one row–column crossing
and the controller sees it.
It turns that into a HID usage code (a standard number meaning "the R key", not the letter R;
for R it's 0x15) and sends it over USB or Bluetooth. Your operating system then applies your
keyboard layout to turn "the R key" into the character r, and passes it to whichever window
is in focus: the browser's address bar.
Debouncing. A physical switch doesn't close cleanly; the contacts bounce several times
within a few milliseconds. The keyboard firmware ignores changes for about 5 ms so that one press
doesn't come out as rrrr.
USB keyboards don't interrupt your computer, they get asked. In USB the host is in charge. Your computer's USB controller polls the keyboard at a fixed interval (every 8 ms for basic keyboards, down to 1 ms for gaming ones) and asks "anything new?". The keyboard answers with an 8-byte report: one byte for modifier keys (Shift, Ctrl…), one reserved byte, and up to six keys currently held down.
// HID boot-protocol report while holding "r"
[ 0x00 ] modifiers (none)
[ 0x00 ] reserved
[ 0x15 ] key 1 ← usage ID for the R key
[ 0x00 0x00 0x00 0x00 0x00 ]
Then the CPU gets an interrupt. When the USB controller has a new report, it raises a hardware interrupt. The CPU stops what it was doing, the kernel's HID driver reads the report and turns it into a key event, and the window system (WindowServer on macOS, Wayland or X11 on Linux, the Win32 input stack on Windows) delivers it to the focused app.
Your keystrokes may leave your computer before you press Enter. With each character, the address bar searches your history and bookmarks, and by default it also sends what you've typed so far to your search engine to get suggestions. If it's fairly sure where you're going, the browser may look up the DNS or even open a connection in advance. So parts of the steps below might already be done by the time you press Enter.
You typed romariostani.com, which is not a complete URL. The browser first has to decide
whether it's a web address or a search. It ends in .com, a real top-level domain, and has no
spaces, so it's treated as an address. The browser then fills in the rest:
https://romariostani.com:443/
│ │ │ └─ path: "/" = the front page
│ │ └───── port: 443 is the default for HTTPS
│ └─────────────────────── host: the name we need an IP address for
└───────────────────────────────── scheme: modern browsers try HTTPS first
HTTPS by default. Browsers used to assume http:// (port 80).
Since around 2021 most browsers try https:// first when you type a bare address, and only fall back if it fails.
HSTS. A site can send a Strict-Transport-Security header meaning "only ever
contact me over HTTPS". The browser remembers that. There is also a preload list built into every
browser, and every .dev and .app domain is on it. For those sites
the browser never even tries plain HTTP.
Checking what it already has. Before using the network, the browser checks whether it has a fresh copy of the page in its HTTP cache, and whether a service worker for this site is installed that could answer the request offline. If neither applies, it continues to DNS.
Networks route traffic by IP address, not by name. So the browser has to find out: "what is the IP address of romariostani.com?" It checks several caches first and only goes further if they don't have the answer:
chrome://net-internals/#dns)./etc/hosts file.1.1.1.1 or 8.8.8.8.
It does the rest of the work for you.If the resolver doesn't have the answer cached either, it walks down the DNS tree from the top:
Your resolver Answer
─────────────────────────────────────────────────────────────────────────
→ root server "where is romariostani.com?"
"Don't know, but the .com servers do:
a.gtld-servers.net ..."
→ .com TLD server "where is romariostani.com?"
"Don't know, but its own name servers do:
ns1.<dns-provider> ..."
→ authoritative NS "where is romariostani.com?"
"romariostani.com. 300 IN A 203.0.113.10"
└─ TTL: cache this for 300 s
The resolver saves the answer for the time given by the record's TTL, hands it to your computer, and now the browser knows where to connect.
Try it yourself. dig +trace romariostani.com in a terminal shows every
step of this walk as it happens.
There are "13 root servers", but really about 2,000. The root has 13 names
(a.root-servers.net to m.root-servers.net). Each name is served by many machines
around the world that share the same IP address. This is called anycast: BGP (see step 7) sends
your packet to the nearest copy. Your resolver rarely needs to ask the root anyway, because it keeps
the .com referral cached for two days.
A and AAAA in parallel. The browser asks for an A record (IPv4) and an
AAAA record (IPv6) at the same time. If it gets both, it tries IPv6 first and starts
IPv4 about 250 ms later if IPv6 hasn't connected yet. Whichever connects first is used. This is called
Happy Eyeballs.
What a query looks like. Classic DNS is one small UDP packet out and one back, with no connection setup. It's also completely unencrypted, so anyone on the path can see which names you look up. That's why DNS-over-HTTPS (DoH) exists: your browser can send the same question inside an encrypted HTTPS connection to a resolver like 1.1.1.1.
DNS query, roughly 40 bytes of payload
ID: 0x3f1a Flags: standard query, recursion desired
Question: romariostani.com type A class IN
Who does which job. The registrar is where you bought the domain. It tells the
.com registry which name servers are authoritative for your domain. The DNS host runs
those name servers and stores your records. These can be the same company or two different ones.
The browser now knows the IP address and wants to send a request. Networks are built in layers, and each layer only deals with its own job. On the way out, each layer wraps what it got from the layer above in its own header, like putting a letter in an envelope, then in a bigger envelope, then in a box. This is called encapsulation. By the time a request leaves your computer it looks like this:
| # | OSI layer | Its job | On this journey |
|---|---|---|---|
| 7 | Application | What the program means | HTTP GET /, DNS queries |
| 6 | Presentation | Encoding, encryption | TLS encryption, UTF-8, gzip/brotli compression |
| 5 | Session | Keeping a conversation going | TLS sessions, HTTP/2 streams |
| 4 | Transport | Between programs: ports, reliability | TCP, from your port 52344 to the server's port 443 |
| 3 | Network | Across networks: IP addresses, routing | IPv4/IPv6, the same source and destination IP from start to finish |
| 2 | Data link | To the next device: MAC addresses | Ethernet or Wi-Fi frames, replaced at every hop |
| 1 | Physical | Bits as a physical signal | Radio waves, voltage on copper, pulses of light in fibre |
The internet doesn't actually run on OSI. OSI is a model for teaching and talking about networks. The real internet uses the simpler TCP/IP model with four layers: Link, Internet, Transport and Application. Layers 5–7 of OSI all fit inside "Application", and TLS doesn't fit neatly into any single layer. Engineers still say "L2", "L3", "L4" and "L7" all the time, so it's worth learning.
Where the size limit comes from. An Ethernet frame can carry at most 1500 bytes (the MTU). Take away 20 bytes of IP header and 20 bytes of TCP header and each packet can hold 1460 bytes of your data (the MSS). Anything larger, like this web page, is cut into pieces of that size, and TCP puts them back together at the other end.
Remember this: the IP addresses stay the same from your computer all the way to the server. The MAC addresses only get the frame to the next device and are replaced at every hop. Most of how networking works follows from this.
Your computer looks at the destination IP and checks its routing table: "Is this address on my local network?" It isn't, so the packet has to go to the default gateway, which is your router.
Here's the catch: to put a frame on the local network you need the router's MAC address, not its IP. So your computer uses ARP and asks everyone on the local network:
ARP Who has 192.168.1.1? Tell 192.168.1.23 (broadcast to everyone)
ARP 192.168.1.1 is at a4:91:b1:0c:22:7e (reply from the router)
Now it builds the frame: source MAC = your network card, destination MAC = the router, but inside it the destination IP is still the server's address. Then it sends it.
Where your IP came from. When you joined the network, DHCP gave your computer
four things: its IP address (192.168.1.23), the subnet mask (/24, which defines what
counts as "local"), the default gateway (192.168.1.1) and which DNS server to use.
Run ip route on Linux or netstat -rn on macOS to see your routing table, and arp -a to see the ARP cache.
On Wi-Fi the frame is an 802.11 frame rather than Ethernet. It's encrypted over the air with WPA2/WPA3 and sent as radio waves at 2.4, 5 or 6 GHz. Wi-Fi is a shared medium, so devices wait for a quiet moment before transmitting (CSMA/CA). This is a big reason Wi-Fi feels less consistent than a cable. The access point turns the frame into a normal Ethernet frame for the router.
IPv6 doesn't use ARP. It uses Neighbor Discovery (NDP) over ICMPv6 for the same job.
Your address, 192.168.1.23, is a private address. Millions of homes use exactly the
same one, so it can't be used on the public internet. Your router fixes this with
NAT (Network Address Translation). It replaces your private address and port with its own
public address and a free port, and writes down the swap in a table:
inside (your LAN) outside (the internet)
Before NAT: src 192.168.1.23:52344 → dst 203.0.113.10:443
After NAT: src 198.51.100.7:61001 → dst 203.0.113.10:443
NAT table: 198.51.100.7:61001 ⇄ 192.168.1.23:52344
When the reply comes back to :61001, the router looks it up in this table and forwards it
to your computer. Then the modem (fibre, cable or DSL) turns the frame into the kind of signal
your ISP's line uses, and it leaves your house.
This is also why your devices are hard to reach from outside. Unless you set up port forwarding, the router has no entry telling it where an unexpected incoming packet should go, so it drops it. NAT was designed to save IPv4 addresses (there are only about 4.3 billion), not as a security feature, but blocking unexpected incoming traffic protects you as a side effect.
CGNAT. Many ISPs don't have enough IPv4 addresses to give each home its own, so they add
another layer of NAT (Carrier-Grade NAT, often in 100.64.0.0/10). Your traffic is then
translated twice. This is one reason hosting a site from home is sometimes impossible.
IPv6 mostly doesn't need NAT. There are 2128 addresses, so every device can have a public one. The router's firewall still blocks unexpected incoming traffic.
There's no single "internet". It's about 80,000 independent networks (called Autonomous Systems, or ASes): your ISP, big carriers, cloud providers, universities. They're connected at Internet Exchange Points and through paid transit links. Your packet goes from router to router, and each one only makes one decision: "which neighbour should I pass this to?"
To make that decision, each router looks up the destination IP in its forwarding table, finds the most specific matching prefix, lowers the packet's TTL by one, wraps it in a new L2 frame for the next link, and sends it. High-end routers do this in dedicated chips in nanoseconds.
Those tables are filled by BGP, the protocol networks use to tell each other which IP ranges they can reach. The global table has around 1 million IPv4 routes.
$ traceroute romariostani.com (example, your path will differ)
1 192.168.1.1 1.2 ms your router
2 100.64.0.1 6.8 ms ISP access network (CGNAT)
3 core1.city.isp.net 8.1 ms ISP core
4 ix-peering.exchange 11.4 ms internet exchange point
5 edge.hostingco.net 19.7 ms the hosting provider's network
6 203.0.113.10 20.3 ms the server
How traceroute works. Every IP packet has a TTL (Time To Live), a counter that each router lowers by 1. When it reaches 0 the router drops the packet and sends back an ICMP "Time Exceeded" message, which exists so that packets can't loop forever. Traceroute uses this on purpose: it sends a packet with TTL=1 (the first router replies), then TTL=2 (the second router replies), and so on, which reveals every hop.
BGP chooses routes by policy, not by shortest distance. A network prefers routes through its customers (they pay it), then through peers (free), then through transit providers (it pays them). The route your packet takes depends as much on business contracts as on geography. Inside a single network, a separate protocol (OSPF or IS-IS, often with MPLS) finds the fastest path.
The speed of light is the hard limit. Light in fibre travels at about 200,000 km/s, two-thirds of its speed in a vacuum. That's about 5 µs per km. From Europe to the US East Coast (~6,000 km) you can never get a round trip below ~60 ms, however good the equipment is. Most intercontinental traffic runs through about 600 submarine cables on the ocean floor.
Packets can take different paths. The request and the response don't have to use the same route, and individual packets can arrive out of order. IP doesn't guarantee anything; making it reliable is TCP's job.
The first packets actually sent over the path in step 7 are the TCP handshake. It's how your computer and the server agree to open a connection. It has three packets:
You Server
│ ── SYN seq=1000 ──▶ │ "Let's talk. I'll start numbering at 1000."
│ ◀── SYN-ACK seq=5000 ack=1001 ─── │ "OK. Got your 1000; I'll start at 5000."
│ ── ACK ack=5001 ──▶ │ "Got it."
│ │
│ ════════ connection open: a reliable byte stream ════════
That costs one full round trip before any real data is sent. What TCP guarantees is that every byte arrives, in order, with nothing missing. It does not provide security. Everything so far could be read by anyone along the path. Encryption comes in the next step.
📦 Want to follow the very first SYN packet through every device on its way? See Part 2.
Ports. Your OS picks a random ephemeral source port (e.g. 52344). The
combination (your IP, your port, server IP, 443, TCP), called the 5-tuple, identifies
this connection among all others on both machines.
Why random starting numbers? Every byte in the stream gets a sequence number. The starting number (ISN) is random so that an attacker can't guess it and inject fake packets into your connection.
The SYN also carries options: the MSS (1460), window scaling (so more than 64 KB can be in flight), SACK (so the receiver can say "I got everything except packet 7"), and timestamps.
How reliability works. The receiver acknowledges the data it has received. If an acknowledgement doesn't arrive in time, the sender sends the data again. Flow control (the receive window) stops the sender from overwhelming the receiver. Congestion control (CUBIC or BBR) stops the sender from overwhelming the network: a new connection starts slowly and speeds up as data gets through, which is called slow start.
Now your browser and the server need to agree on a secret key that nobody listening on the path can work
out, and your browser needs proof that it's really talking to romariostani.com and not an
impostor. TLS 1.3 does both in one round trip:
You Server
│ ── ClientHello ──▶ │
│ supported ciphers, a random value, │
│ key_share (my half of a key exchange), │
│ SNI = romariostani.com, ALPN = [h2, http/1.1]
│ │
│ ◀── ServerHello + key_share (the server's half) ─── │
│ ─ ─ ─ ─ from here on everything is encrypted ─ ─ ─ ─
│ ◀── 🔒 Certificate "I am romariostani.com" ─── │
│ ◀── 🔒 CertificateVerify (signature to prove it) ─── │
│ ◀── 🔒 Finished ─── │
│ ── 🔒 Finished (+ the HTTP request right after) ──▶ │
Both sides combine their two halves using Elliptic-Curve Diffie-Hellman and get the same shared secret, even though the secret itself was never sent over the network. All data after this is encrypted with that key using AES-GCM or ChaCha20.
How your browser decides to trust the certificate. The server sends a chain of certificates:
romariostani.com ← signed by → intermediate CA (e.g. Let's Encrypt R11)
intermediate CA ← signed by → root CA (e.g. ISRG Root X1)
root CA ← already in your OS / browser trust store
The browser checks each signature up the chain until it reaches a root it already trusts. It also checks that the name matches, that the certificate hasn't expired, and that it has been recorded in the public Certificate Transparency logs. Click the padlock in your address bar to see this chain.
Proving it owns the certificate. A certificate is public, so anyone could send a copy.
In CertificateVerify the server signs the whole handshake with the private key that
matches the certificate. Only the real owner of that key can produce this signature.
SNI (Server Name Indication) tells the server which site you want, because one IP address often hosts thousands of sites. It's sent before encryption starts, so your ISP can see which site you visit even though it can't see what you do there. Encrypted Client Hello (ECH) is starting to fix this.
Forward secrecy. A new key pair is created for every connection and thrown away afterwards. Even if the server's private key is stolen years later, recorded traffic from today still can't be decrypted. Recent browsers also mix a post-quantum algorithm (ML-KEM) into the key exchange, to protect against future quantum computers.
HTTP/3 merges steps 8 and 9. HTTP/3 runs over QUIC, which is built on UDP and
combines the transport handshake and the TLS handshake into one round trip. Your browser usually learns from
an Alt-Svc header that a site supports it and uses it from the next visit onwards.
Now, inside the encrypted connection, your browser finally asks for the page. With HTTP/2 the request is sent as compact binary frames, but written out as text it looks roughly like this:
:method: GET
:scheme: https
:authority: romariostani.com
:path: /
user-agent: Mozilla/5.0 (Macintosh; …) … ← which browser you are
accept: text/html,application/xhtml+xml,… ← what formats you understand
accept-encoding: gzip, deflate, br, zstd ← compression you support
accept-language: en-US,en;q=0.9
sec-fetch-mode: navigate ← you navigated here yourself
In plain words: "Please give me / from romariostani.com. I understand
HTML, and you can compress it with Brotli."
HTTP/1.1 vs HTTP/2. HTTP/1.1 is plain text (GET / HTTP/1.1\r\nHost: …),
one request at a time per connection. HTTP/2 sends several requests at the same time as numbered
streams over one connection, and compresses headers (HPACK) because most of them are the same on
every request. The version was chosen during the TLS handshake through ALPN.
Your actual request. Open DevTools (F12) → Network tab → reload → click the first row. You'll see these headers and all the timings from the panel at the top of this page.
On the server the path goes back up through the layers. The network card receives the frame and copies it
into memory. The kernel checks the IP and TCP headers, puts the data in order, and passes the bytes to the
program listening on port 443: the web server. The web server decrypts the TLS, reads
GET /, maps / to a file named index.html, and replies:
:status: 200 ← OK, here it is
content-type: text/html; charset=utf-8
content-encoding: br ← compressed with Brotli
cache-control: max-age=300 ← you can reuse this for 5 minutes
<!DOCTYPE html><html lang="en">… (this very page)
How one server handles thousands of connections. Web servers like nginx or Caddy don't
create a thread per visitor. They use an event loop: the kernel (through epoll on Linux)
tells them which connections have data ready, and they handle each one quickly without waiting on the others.
The file probably wasn't read from disk. The OS keeps recently used files in RAM (the page cache), so a popular static page is served straight from memory.
Status codes. 200 OK · 301/302 go somewhere else ·
304 your cached copy is still valid · 404 not found ·
500 the server made a mistake · 503 the server is overloaded or down.
The response goes through everything above in reverse. It's split into packets of about 1460 bytes,
routed back across the internet (possibly along a different path), and translated back through your router's
NAT table to 192.168.1.23:52344. Your computer's TCP stack reassembles the packets in order,
TLS decrypts them, and the browser receives the HTML.
The 14 KB rule. Because of TCP slow start, a new connection may only send about 10 packets (~14.6 KB) before it has to wait for an acknowledgement. That wait costs a full round trip. A page that fits in those first ~14 KB after compression arrives in a single round trip, which is one reason fast sites keep their first HTML small. This page was — over the wire.
The connection stays open. After the page arrives, the browser keeps the TCP/TLS connection open for a while. Any further request to the same site skips steps 3, 8 and 9 entirely.
Now the browser has to turn text into a page you can see. It does this in stages:
bytes ──▶ characters ──▶ tokens ──▶ DOM tree (the structure of the HTML)
CSSOM (the styles in <style>)
│
DOM + CSSOM ▼
render tree what should be visible
▼
layout where exactly, and how big
▼
paint what to draw: text, colours, borders
▼
composite the GPU combines the layers into one image
Then the small script at the bottom of this page ran, asked the browser for its timings, and drew the panel at the top.
Your browser is several programs. Chrome, for example, has a browser process (the tabs and address bar), a network process (steps 3–12), a GPU process, and a sandboxed renderer process for each site that parses HTML and runs JavaScript. If this page crashed, only this tab would die.
It starts before the download finishes. The HTML parser works on bytes as they arrive, and a preload scanner looks ahead for images, scripts and stylesheets so it can request them early. This page has none of those, which is part of why it loads fast.
JavaScript can block parsing. A plain <script> tag stops the HTML parser
until the script has downloaded and run, because the script might change the page. That's why scripts
usually use defer or sit at the end of the page, like the one here.
The GPU writes the finished image into a block of memory called a framebuffer. At the next screen refresh (every 16.7 ms at 60 Hz, 8.3 ms at 120 Hz) it's sent over HDMI, DisplayPort or the laptop's internal cable to the display. The panel then sets each of millions of red, green and blue subpixels to the right brightness.
Light leaves the screen, hits your retina, and your brain turns it into the word you're reading now.
The journey started with an electrical signal from your finger and ends with one in your eye. 🔁
Part 1 showed the journey from far away. Here we zoom in on a single packet: the very first
SYN of the TCP handshake. We follow it through every device between your laptop and the server
and watch what each device does to its headers. Fields that changed at a hop are
highlighted.
This is one realistic example: a home on fibre (GPON) with an ISP that uses PPPoE, CGNAT and an MPLS core, reaching a server in a data center through an Internet Exchange. Your own path may be cable or DSL, may have no PPPoE or CGNAT, may use IPv6, and there may be many more routers in the core. The addresses are example ranges.
| Field | Changed? | Where |
|---|---|---|
| Destination IP & port | Never | 203.0.113.10:443 from start to finish |
| Source IP & port | 2 times | Home router NAT, then the ISP's CGNAT |
| TTL | 8 times | Every router: 64 → 56. Switches and the IXP don't touch it. Inside MPLS the label's own TTL counts down and is copied back into the IP header at the end |
| MSS option | Once | Home router lowered it to fit PPPoE (MSS clamping) |
| L2 header (MACs) | At every hop | Every link gets a brand-new frame |
| Extra wrappers | Added and removed | 802.11 + WPA3, VLAN, PPPoE, GEM, outer VLAN, MPLS ×2, VXLAN |
| Sequence number | Never* | *Some firewalls and load balancers do rewrite it |
Terms with a dotted underline anywhere on this page can be clicked to show their definition.