← blog

A Web Request, Layer by Layer

What happens after I open a website? Follow one request from a name lookup to encrypted messages and packets, then see why reused connections can reset, a working site can return 502s, and large uploads can stall.

I type https://example.com/hello and press Enter. Somewhere a server has the page I want. Between us sit my laptop, a Wi-Fi router, a dozen machines I will never see, and a cable under an ocean. None of them knows what a page is.

So the first job is not delivery. It is agreement. Two programs that have never met need one exact form for “send me this” and one exact form for “here it is”, down to the byte where one field ends and the next begins. That agreed form is a protocol, and the web’s is HTTP.

Everything else here is what it takes to carry that agreed message across the machines in between without it being lost, reordered, altered or read. Each of those is a separate job with its own protocol, and every request carries all of them at once, one wrapped inside the other. That is the sentence to keep: HTTP, TLS, TCP and IP are not alternatives to each other. They are layers.

First, agree on what the message means#

HTTP (the web’s request-and-response protocol) gives the browser and server that agreement. In HTTP/1.1, a request can be this small:

GET /hello HTTP/1.1
Host: example.com

GET means “send me this resource.” /hello identifies it. Host names the site, since one server can serve several names. An empty line ends the headers (the message’s descriptive fields). HTTP/1.1 uses \r\n line endings on the wire.

The server understands those rules and can answer:

HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Content-Length: 16

hello, internet!

200 reports success. Content-Type says how to interpret the body; Content-Length gives its length in bytes. This body is 16 bytes, with no newline after !. Including headers and line endings, the reply is 96 bytes. These are example messages, not a capture from example.com. HTTP/1.1 message format.

It really is just text. A plain socket, no HTTP library, is enough to speak it:

try (var socket = new Socket("example.com", 80);
     var out = socket.getOutputStream();
     var in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {

    out.write("GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n"
              .getBytes(StandardCharsets.US_ASCII));
    out.flush();
    in.lines().forEach(System.out::println);
}

The socket carries bytes; the request text supplies HTTP’s rules. For HTTPS, the socket must also protect those bytes and check the server’s identity. The connection setup becomes:

try (var socket = (SSLSocket) SSLSocketFactory.getDefault()
        .createSocket("example.com", 443)) {
    var parameters = socket.getSSLParameters();
    parameters.setEndpointIdentificationAlgorithm("HTTPS");
    socket.setSSLParameters(parameters);
    socket.startHandshake();
}

443 selects the HTTPS service. The default factory uses the configured certificate trust, and the HTTPS identification setting adds the hostname check. startHandshake() must succeed before I send the same HTTP request through this socket. This snippet only establishes and closes the connection; the write/read operations from the first example belong inside it. A factory swap without the name check is not equivalent to a browser’s verification. Java TLS socket verification

The HTTP/1.1 message format stays the same. TLS supplies its protected transport.

Agreeing on words does not deliver them#

Writing a letter and delivering it are separate jobs. The words can be perfectly clear while the letter gets lost, goes to the wrong address, or is opened along the way. An HTTP message has the same problem.

A layer is one part of the delivery system, with a specific responsibility. Its protocol defines how the two sides perform that job.

Problem still to solveJob added below HTTP
Other machines carry my message. Can they read or alter it?TLS protects the conversation and checks the server’s identity.
Pieces may go missing or arrive out of order.TCP tracks bytes, restores their order and retries losses.
The server is on another network. Where is this going?IP gives packets source and destination addresses.
My laptop can only transmit over its local connection.Ethernet or Wi-Fi delivers each local handoff.
Bytes in memory cannot cross a cable or the air by themselves.Physical hardware encodes them as signals.

One giant protocol could combine these jobs. Keeping them separate means a new application can reuse delivery and security instead of building both. It also means changing one part needn’t change all the others.

Same request, same upper protocolsHTTP · TLS · TCP · IPcarry IP packetcarry IP packetWi-Firadio signalsEthernetcopper or fibre here
Changing the local connection doesn’t change what GET means. The link’s job is to carry the packet, so HTTP doesn’t need separate Wi-Fi and Ethernet versions.

The separation also gives choice: DNS can use a different transport from HTTP, and TLS can protect email as well as web traffic. The cost is extra headers and coordination. Layers are useful boundaries, not sacred ones; HTTP/3 will combine some of these jobs differently.

Now I can follow the actual exchange. My laptop is already online. I’m using a fresh HTTPS connection with HTTP/1.1, TLS 1.3 and TCP, and illustrative IP addresses. HTTP/2 uses binary framing instead of HTTP/1.1’s text; HTTP/3 also changes the transport.

Before the request can leave#

The URL contains three useful instructions:

PieceWhat my browser does with it
https://Use HTTP over a secure connection; port 443 by default
example.comFind an IP address and verify that the server can act for this name
/helloAsk that server for this resource

DNS (the name-to-address system) does the lookup. A resolver (a service that finds DNS answers) returns an address such as 198.51.100.7. It’s like looking up a contact’s phone number: it tells me where to call, not what to say.

A cached answer may be enough. Otherwise the resolver may consult other DNS servers. A DNS TTL (how long an answer may be cached) limits how quickly an address change is normally noticed; it does not itself cause an outage. The /hello path is not part of the lookup. DNS has its own messages and transport: commonly UDP or TCP, or an encrypted connection such as DNS over HTTPS. It isn’t a wrapper around my web request. DNS explanation.

One cached answer, TTL 300 seconds:

0 s                 60 s                             300 s
cache old IP        authoritative answer changes     cached answer expires
                    └─ this cache may still use old IP ─┘

For a planned move, I keep the old destination working while caches drain. Lowering the TTL must happen before the move, because already-cached answers retain their earlier lifetime. An outage depends on whether the old destination still works, not just the TTL. Existing connections have their own lifetime too. Route 53 TTL behavior

Four jobs before the reply

My browser needs an IP address for example.com. A cached answer can skip this exchange; otherwise a resolver finds it.

My laptop → resolverexample.com?
Resolver → my laptop198.51.100.7
ResolverMy laptopWeb serverexample.com?198.51.100.7SYNSYN-ACKACKClientHelloServer handshakeFinishedGET /hello200 + body
Select a stage to highlight its messages. Time runs downward; the resolver is a separate service from the web server. Messages are grouped for clarity, not drawn as individual packets.

TCP: agree to keep track#

An IP address reaches a host. A port (a numbered endpoint on that host) helps reach the right service: 443 for HTTPS here. My laptop chooses a temporary port, say 51203, for its end.

TCP begins with a handshake (an opening exchange): SYN, SYN-ACK, ACK. The endpoints exchange starting byte numbers and connection options, then confirm they can hear each other. There is no reserved cable between them; each endpoint keeps TCP state. NAT devices and stateful firewalls can keep separate connection-tracking state along the path.

That state will let them track which bytes arrived and resend missing ones. It does not establish the website’s identity or encrypt anything. TCP specification.

The socket can outlive the path’s memory#

A connection pool (a set of sockets kept for reuse) can hold a socket after an intermediate device has forgotten its flow. AWS NAT Gateway expires an idle connection after 350 seconds and returns an RST (an abrupt TCP reset) when the private-side client tries to continue it. NAT idle expiry

A reused TCP connection fails after AWS NAT Gateway expires its idle mappingApp connection poolAWS NAT GatewayRemote server1 · A connection worked, then went quietThe app keeps a socket for reuse.The NAT gateway keeps a mapping for its traffic.2 · After 350 seconds without trafficThe NAT mapping expires.A socket in the app’s pool can still appear open.Send on old connectionA later reuse attemptRST: reset connectionThe old request is not forwarded3 · Recover with a new connectionNew TCP handshakeCreate fresh connection stateForward handshakeA new NAT mapping carries it
The reset comes when the app tries to reuse the expired connection, not as a graceful FIN at the instant the timer expires. Arrows group exchanges; scroll sideways on a narrow screen.

I set pool idle eviction below the shortest relevant path timeout, or use supported keepalives with enough margin to keep the needed state alive. A failed connection must be replaced; retrying an operation also requires knowing it is safe to repeat. Keepalives are not one universal fix: an HTTP load balancer can require application data rather than TCP probes.

AWS Network Load Balancer has a 350-second default for TCP flows, but its TCP-listener timeout is configurable; TLS listeners differ. I inspect the actual listener and path rather than treating 350 seconds as a TCP rule. NLB idle settings

Temporary ports are another limit. Linux documents a default automatic port range of 32768–60999: 28,232 choices before reservations. A connection is identified by both endpoint addresses and ports, so this is not a universal total-connection limit. Repeated short connections to one destination can run out of available combinations, especially while TIME_WAIT (state retained after an active close) delays reuse. I reuse connections and inspect port allocation before reaching for kernel tuning. Linux port allocation

TLS: establish trust and keys#

TLS (the protocol that protects the conversation) has two different problems to solve: agree on secret keys, and make sure the other end can act for example.com.

Agree on keysExchange public key shares.Keep private values private.Check the server’s identityCertificate: trusted for this name?Signature: has the private key?keys derivedproof verifiedFinish handshake; send protected HTTP
Key agreement and identity checks solve different problems. An encrypted connection to an impersonator would still be the wrong connection.

There is a puzzle in the first of those. How can my browser encrypt anything for a server it has never spoken to? It cannot, and it does not try. The ClientHello goes out in the clear, carrying the browser’s half of a key agreement: think of two people who each mix a private colour into the same base paint and swap the results in public. Each adds their own private colour to the other’s mix and lands on the same final shade, while anyone watching the swap holds two mixes and no way to un-mix them.

In a typical fresh TLS 1.3 handshake, those public key shares travel in ClientHello and ServerHello. Each side combines the other’s share with its own private value to derive shared secrets. The keys themselves aren’t sent.

The browser checks the certificate’s name, validity and chain of trust, plus the server’s signature proving possession of the corresponding private key. Finished messages check the handshake’s integrity. Application data then travels in encrypted records (protected blocks of bytes), with authentication tags that detect tampering. TLS 1.3.

TLS hides the request’s path and content from ordinary forwarding devices. It doesn’t hide destination IP addresses, sizes or timing. The hostname may also be exposed through DNS or the handshake, depending on which protections are used.

One message, several delivery jobs#

The reply travels back through the same kinds of layers as the request. I can stop it inside the server and watch each job add its information.

Watch the wrappers add up
HTTP / encrypted contentTLS header + tagTCPIPEthernet

96 bytes: status line, headers, blank line, and the 16-byte body. This is the message before encryption.

Choose a row to inspect its size. Bar lengths share one scale; the larger bar contains the smaller layer’s bytes, not another copy of them.

TLS protects the content. In this example, 96 HTTP bytes become a 118-byte TLS record: 5 bytes of header, 96 encrypted content bytes, an encrypted 1-byte content type, and a 16-byte authentication tag. This assumes no padding and a cipher with a 16-byte tag.

TCP numbers the bytes. Its header identifies the ports and where this piece belongs in the stream. A segment (a piece of the TCP stream with its header) doesn’t have to contain a whole HTTP message or a whole TLS record.

IP supplies the destination. Its header carries source and destination IP addresses. A packet (an IP header and its contents) can cross many networks. IP doesn’t promise arrival or order; that is why this connection also needs TCP.

Ethernet handles the local handoff. A frame (a link’s header and payload, plus its error check) names the sending and next receiving interfaces using MAC addresses (addresses used on the local link). Wi-Fi does a similar job with a different frame format.

The physical link carries signals. The network hardware encodes the frame into electrical, optical or radio signals. A bit isn’t necessarily one pulse; the encoding depends on the link technology.

The sizes aren’t universal. This example uses minimal 20-byte IPv4 and TCP headers and an untagged Ethernet frame. Together they make 176 bytes, excluding Ethernet’s preamble and the gap between frames.

The next device is not the final destination#

A parcel keeps its destination address as couriers hand it onward. Each handoff still has an immediate recipient. IP identifies the destination; the link header identifies the next local recipient.

ServerRouterLaptopframe to routerframe to laptopMAC: server → router inputMAC: router output → laptopIP destination: 203.0.113.9IP destination: 203.0.113.9TTL: 64TTL: 63TCP + protected TLS bytesSame TCP + protected TLS bytesNew local addresses. Same destination. One fewer hop allowed.
Two consecutive Ethernet links, with no address translation. The router replaces the local frame; the protected content continues toward the same IP destination.

The router reads the destination IP and looks up the next hop. It decreases TTL (the packet’s remaining hop allowance), updates the IPv4 header checksum, and sends a new frame on the outgoing link. TTL stops a packet circulating forever. A switch normally forwards within a link using MAC addresses. IPv4 forwarding.

Finding the next interface’s MAC address is another local lookup: ARP for IPv4, Neighbor Discovery for IPv6. For a remote server, my laptop looks up its gateway’s MAC, not the server’s.

These are forwarding roles, not limits on what hardware can inspect. The table shows what each job needs to understand; a device can inspect more when the bytes are visible:

Box on the pathOpens up toWhat it changes
Switch, Wi-Fi access pointEthernetnothing; forwards by MAC
RouterIPnew frame, TTL minus one, new IP checksum
Home router doing NAT (address translation)TCP/UDPrewrites address and port so many devices share one address
FirewallIP and ports; some inspect plain HTTPdrops by address, port, protocol
Load balancerports, or TLS and HTTPmay terminate TLS and read the path
CDN edge (a service that serves content near users)HTTPterminates TLS, answers from cache, opens its own connection to the origin
My laptop’s kernelTCPstrips each header, hands bytes to the socket for port 51203
My browserTLS and HTTPdecrypts, parses, renders

“Encrypted end to end” depends on where those ends are. A CDN that terminates TLS is one of them.

That boundary decides how I balance traffic. An L4 balancer (one that routes transport connections) uses addresses and ports; it does not route HTTP paths. An L7 proxy (one that understands application messages) can terminate TLS and inspect Host and /hello, then open a separate backend connection. That enables HTTP routing or request filtering; caching depends on the chosen product.

ConnectionWho can read HTTP?Client identity at the backend
TCP forwarding, TLS ends at the backendBackendSource IP preservation depends on the forwarding mode
TLS ends at an HTTP proxy; another connection reaches the backendProxy and backendOften the proxy’s socket address; original client IP can be conveyed separately

An ALB can pass the original address in X-Forwarded-For. I trust forwarded identity only from known proxies with a defined header policy; accepting an arbitrary caller’s header would let them invent their address. PROXY protocol is a different option supported by some other setups. ALB forwarded headers

How routers learn their routes, including BGP between networks, is a separate question. My trace to 1.1.1.1 follows that part.

The bytes underneath the picture
Inspect the 176-byte example

A constructed example, not a packet capture. These are three Ethernet links with no NAT. Ciphertext is illustrative; IPv4, TCP and Ethernet checksums are calculated. Real captures often omit Ethernet’s trailing checksum.

simulated linkserver NIC to the rack's router
inspect
0000021a4b100001021a4b10000708004500..K.....K.....E.
0010009e3a7c400040069999c6336407cb00..:|@.@....3d...
0020710901bbc8039e3c4f015b12a0e45018q......<O.[...P.
003001f57f8000001703030071a74bfb0415..........q.K...
0040160e081e2fca11cc7c3f0a77139c7723..../...|?.w..w#
00505d3993833e1853bfdf932000df0b9b81]9..>.S... .....
00602e27ff7295113aa18ff7fa32a23ebf0a.'.r..:....2.>..
007083fce3fe8e4ad9651af26f12b70f1ec6.....J.e..o.....
0080bf478ff011bbdc7224c6fb83e521acef.G.....r$....!..
0090ee0d7b74ab5805c36eae2df42eac0e7d..{t.X..n.-....}
00a0ad364a21ef66f63673ac7dea5818c2d3.6J!.f.6s.}.X...
176 bytes in this Ethernet frame16 bytes of message91% wrapping
IPv4layer 3
Who reads it: every router on the path.
In transit: in this model: addresses stay fixed, TTL decreases, checksum changes.
version / header length45IPv4, 5 words = 20 bytes
DSCP / ECN000 = no special treatment asked
total length00 9e158 bytes, header through payload
identification3a 7c0x3a7c, used only if fragmented
flags / fragment offset40 00DF set: do not fragment me
TTL4064 hops left before a router drops it
protocol066 = TCP is inside (17 would be UDP)
header checksum99 990x9999, recomputed each hop because TTL changed
source addressc6 33 64 07198.51.100.7 (the server)
destination addresscb 00 71 09203.0.113.9 (the laptop)
Optional detail: expand the explorer, choose a layer, then change the link. Outlines identify changed bytes.
The bytes underneath the picture
Optional detail: expand the explorer, choose a layer, then change the link. Outlines identify changed bytes.

What if part of the reply disappears?#

TCP is like receiving numbered pages: page 3 may arrive before page 2, but reading them in arrival order would change the story. TCP tracks byte positions, acknowledges progress and retransmits missing data.

A gap in the byte stream
The middle piece is lost

Bytes 1–4 arrive. Bytes 9–12 also arrive, but TCP keeps them waiting because bytes 5–8 are missing.

14delivered
58lost
912waiting
Available to TLS: bytes 1–4
Step through a loss and recovery, or play it once. Byte numbers are relative to the start of this example; real TCP sequence numbers begin at negotiated offsets.

That ordering has a cost: later bytes wait behind a gap, even if they have arrived. TCP also adjusts its sending rate to avoid overwhelming the receiver or network. It cannot guarantee eventual delivery through a permanent failure. TCP behavior.

Why a large reply becomes many packets

A link has an MTU (the largest IP packet it carries). With a 1,500-byte MTU and minimal IPv4 and TCP headers, at most 1500 − 20 − 20 = 1460 TCP payload bytes fit. Options, IPv6 and tunnels change that budget.

HTTP can stream a body, TLS protects blocks of it, and TCP carries the resulting stream in segments. The whole response doesn’t need to exist in memory first. One HTTP message can span many TLS records and segments; a socket read can return only part of a record.

A small request works; the upload hangs#

Path MTU (the smallest packet limit along a route) can be lower than the local interface’s limit. For example, VXLAN over IPv4 adds 50 bytes around an inner IP packet, including its inner Ethernet header. With a 1,500-byte underlying MTU, that leaves 1,450 bytes for the inner IP packet. This is one overlay configuration, not every Kubernetes network; Calico documents different budgets for IPv6 and other tunnel types. Calico MTU choices

Large IPv4 packets are dropped while blocked ICMP hides the smaller path MTUSenderFirewall on pathSmaller next hopMTU 14501 · Small packets fitSmall request passesBelow the path MTU2 · A larger packet cannot fit1500-byte IPv4 packetDF set: routers must not fragment itICMP: smaller MTUFragmentation needed; MTU 1450The firewall drops the ICMP feedback.Without working discovery or probing, large transfers can stall.
This is an illustrative IPv4 path, not a claim that every tunnel behaves identically. Successful small requests do not prove that larger packets can cross the same path.

I configure the workload and tunnel MTUs for their actual overhead, and permit the ICMP feedback that discovery needs: IPv4 “fragmentation needed” or IPv6 “Packet Too Big.” Implementations with packet-size probing can sometimes recover without that feedback, but blocking it can create a PMTU black hole (large packets disappear without usable size feedback). IPv4 discovery, IPv6 discovery

Back into the browser#

At my laptop, the network hardware receives the frame. The operating system handles IP and TCP, finds the connection using its addresses and ports, and supplies an ordered byte stream through a socket (the program’s interface to the connection).

sequenceDiagram
  participant K as OS / TCP
  participant T as Browser TLS
  participant H as Browser HTTP
  K->>T: ordered bytes
  Note over T: Buffer a record<br/>Decrypt and verify
  T->>H: HTTP bytes
  Note over H: Parse headers<br/>Consume body
Receiving unwinds the jobs. TCP restores order, TLS verifies and decrypts, and HTTP interprets the reply.

The browser gets hello, internet!. If the body were HTML, parsing it could trigger more requests for images, styles and scripts. The same connection can carry later requests; opening a new connection for every file would repeat expensive setup.

For a simple fresh TCP + TLS 1.3 connection, the first response takes roughly three round trips: TCP setup, TLS setup, then the HTTP exchange. At an assumed 50 ms round-trip time, that’s about 150 ms, before DNS, server work or losses. Reusing an established connection skips the first two. With no retry or loss, HTTP/3 can reduce this to roughly two round trips for a fresh connection. A resumed connection with accepted early data can reach one; neither figure includes DNS or server work.

The proxy and backend need compatible clocks#

Reusing a connection assumes the other end will still accept a request. An ALB defaults to a 60-second idle timeout. If my backend closes its idle socket earlier, a request can race with that closure and produce a 502 Bad Gateway (the proxy could not obtain a usable backend response).

ALBBackendidle timeout: 60 sidle timeout: 30 s0 s · last exchange finishes30 s · closes socketNew request on old socketClose notification still in flightNo usable backend response → possible 502Prefer: ALB idle timeout < backend idle timeout
An illustrative race, not a guaranteed failure at 30 seconds. The request and close notification cross in flight before the ALB learns that the backend socket is gone.

AWS recommends an application idle timeout larger than the ALB’s. For example, I might choose 75 seconds at the backend and 60 at the ALB, with a margin appropriate to the system. That addresses this race, not every cause of 502. The ALB’s separate HTTP client keepalive-duration setting is not this idle timer, and TCP keepalive probes do not substitute for application data to reset it. ALB timeout behavior, ALB troubleshooting

Where the seven-layer names fit#

The OSI model (a seven-layer reference model for communication) names these responsibilities. I find the numbers useful once each job has a concrete place in the request.

OSI layerThe job in this story
7 · ApplicationHTTP asks for a resource; DNS looks up a name
6 · PresentationRepresent and protect data; TLS covers part of this role
5 · SessionManage a conversation; this web stack has no separate session-layer protocol
4 · TransportTCP delivers an ordered stream between endpoints
3 · NetworkIP addresses packets across networks
2 · Data linkEthernet or Wi-Fi carries a frame across a local link
1 · PhysicalHardware carries the bits as signals

This is a map of responsibilities, not a requirement for seven headers. TLS doesn’t fit neatly into one numbered box. Neither does QUIC (a secure transport carried over UDP), which HTTP/3 uses in place of TCP and the separate TLS record layer.

What HTTP/3 changes about the stack
HTTP/1.1 or HTTP/2 over HTTPSHTTP/3
HTTP → TLS records → TCP → IP → linkHTTP/3 → QUIC → UDP → IP → link
One ordered TCP byte streamSeveral independently ordered QUIC streams
Missing bytes hold up later bytes in that TCP streamA gap in one stream need not hold up delivery on another

UDP supplies ports and datagrams (separately delivered messages), without TCP’s ordering or retries. QUIC implements reliable streams, loss recovery and congestion control above it, and uses the TLS 1.3 handshake to establish keys. UDP didn’t become reliable; QUIC added the missing jobs. Using UDP makes QUIC easier to deploy through existing networks than a new IP transport number, though some networks still block UDP and require a fallback.

QUIC integrates transport setup with TLS key establishment, so a normal fresh handshake takes one round trip. A return visit may use a previous ticket for 0-RTT (sending application data before waiting for the new handshake), if both sides permit it. The server can reject early data. TLS in QUIC

Early data has a replay risk: a captured request might be processed more than once. A client must not assume an unsafe operation is suitable; even a GET can have effects if the application misuses it. The server can reject early data, delay processing, or return 425 Too Early so the client retries after the handshake. “Return visit” is not permission to replay a payment. HTTP early-data rules

Streams still share network capacity and congestion control. HTTP dependencies can cause waiting too. QUIC removes a particular kind of transport-level blocking, not every reason a page can stall. QUIC’s design.

The question I keep beside this stack is: what is each piece responsible for? HTTP understands /hello. TLS protects the conversation. TCP notices missing bytes. IP carries the destination. The local link gets the packet to the next device. None of them has to do every job.

Comments

Signed in with GitHub. Be kind.