2.1
HTTP/1.0 to HTTP/3, and the Other Application Protocols
What each version fixed: persistent connections, multiplexing, header compression, QUIC; why server push died; where SMTP, SSH and friends fit.
55 min Reading + animations
Tip: tap or hover any dotted-underlined term to see what it means.
By the end of this lesson you will be able to
- Explain what each HTTP version changed and why: connection per request (1.0), persistent connections,
Hostand chunking (1.1), binary framing and multiplexing (2), QUIC streams (3). - Trace head-of-line blocking through all four versions and say exactly which kind each version removes and which remains.
- Describe HTTP/2 internals an interviewer will ask about: frames, streams, per-stream flow control, HPACK, priorities, connection coalescing,
GOAWAY. - Explain what HTTP/3 adds on top of QUIC: QPACK, 0-RTT and its replay risk, connection migration, discovery through Alt-Svc and the DNS HTTPS record, and the UDP fallback.
- Say how client and server agree on a version (ALPN), why server push died and what 103 Early Hints does instead.
- Place HTTP among the other application protocols: ports, the email path (submission, MX, relay, IMAP) with SPF/DKIM/DMARC, SSH vs Telnet, FTP vs SFTP, NTP, SNMP, DNS over TLS/HTTPS.
- Turn this into design decisions: which version on which hop, load balancing long-lived connections, and the failures each version brings.
#Where this lesson fits
HTTP (Hypertext Transfer Protocol) is the foundation of communication on the web. It is an application-layer (layer 7) protocol that controls how a client, usually a browser or an app, exchanges information with a server. To make web applications fast, secure and efficient you need to understand how HTTP evolved, and then its methods, status codes, headers, caching and compression. This lesson covers the evolution; the next three cover the rest:
- Lesson 2.2: methods and status codes.
- Lesson 2.3: headers, keep-alive settings, compression and CORS.
- Lesson 2.4: caching.
You already know the pieces underneath. Lesson 1.1 showed what an HTTP message looks like and gave a first table of the versions. Lesson 1.9 taught TCP, TLS 1.3, 0-RTT and QUIC, and lesson 1.10 showed why a new TCP connection is slow (handshake plus slow start). This lesson does not re-teach those; it shows how each HTTP version uses them.
One idea runs through the whole history: HTTP's meaning never changed, only its wire format did. A GET /cart with Accept: application/json that returns 200 OK means the same in HTTP/1.0 and HTTP/3. The IETF even split the standards in 2022 accordingly: RFC 9110 defines the semantics once, and RFC 9112, 9113 and 9114 define how HTTP/1.1, HTTP/2 and HTTP/3 carry them. Every version since 1996 is an answer to the same two questions: how do we avoid paying for new connections, and how do we stop one slow or lost thing from holding up everything else?
| Version | Year | One-line summary |
|---|---|---|
| HTTP/1.0 | 1996 | One TCP connection per request, closed after the response; no reuse |
| HTTP/1.1 | 1997 | Persistent connections by default, pipelining (never used in practice), Host header, chunked transfer |
| HTTP/2 | 2015 | Binary framing, many streams multiplexed on one TCP connection, HPACK header compression |
| HTTP/3 | 2022 | The same ideas over QUIC on UDP: no TCP head-of-line blocking, faster setup, connection migration |
One page, four HTTP versions: where the round trips go
The page is index.html plus four files. Each lane is a connection (or a stream inside one). Time is in round trips; 1 RTT = 50 ms. Pick a version and press play.
Scenario
A new connection for every file, closed after one response. The browser runs two connections in parallel, as browsers of that era did.
Connection 1 starts with a TCP handshake: one full round trip before a single byte of HTTP can move.
#HTTP/1.0: one request, one connection
Before HTTP/1.0 there was HTTP/0.9 (1991): one line, GET /page.html, and the server sent back raw HTML and hung up. No headers, no status codes, no images. HTTP/1.0 (RFC 1945, 1996) wrote down what browsers and servers had grown into: a request line with a version, headers, status codes, Content-Type, so a response could be an image, not just HTML.
Its connection model was the weak point. Every resource (the HTML, each CSS file, each image) got a new TCP connection, and the server closed it after the response. Closing was not only tidiness: without a length header, closing the connection was how the client knew the body had ended.
Browsers fought this by opening several connections in parallel (two to four at the time), and some servers supported an unofficial extension: the client sent Connection: keep-alive and the server, if it agreed, kept the connection open. That extension is the seed of HTTP/1.1.
#HTTP/1.1: persistent connections and their limits
HTTP/1.1 (RFC 2068 in 1997, revised in 1999 and again as RFC 9112 in 2022) is still everywhere: in curl, between load balancers and backends, in countless APIs. It made five changes that matter.
#Persistent by default
A connection stays open after a response, and the next request reuses it, with no new handshake and with TCP's congestion window already warm. Persistence is the default; only Connection: close ends it. (So "keep-alive was introduced by HTTP/1.1" is not quite right: HTTP/1.0 had it as an opt-in extension; HTTP/1.1 made it the rule.)
For the next response to be found, every response must say where its body ends: either Content-Length, or chunked encoding (below). Servers close connections that stay idle too long (often 5 to 75 seconds) to free memory. Lesson 2.3 covers the keep-alive headers and timeouts in detail.
#Pipelining, and why it failed
HTTP/1.1 also allowed pipelining: the client sends request 2 and 3 without waiting for response 1. It sounds like multiplexing, but HTTP/1.1 messages carry no request ID. The only way to match responses to requests is order, so the server must answer in exactly the order it received the requests. If request 1 is slow (a database query, a large file), responses 2 and 3 wait behind it even if they were ready in a millisecond. This is HTTP-level head-of-line (HoL) blocking.
Worse, many proxies and servers mishandled pipelined requests, and a client cannot safely retry a pipelined non-idempotent request after a broken connection. So no major browser ever enabled pipelining by default. In practice HTTP/1.1 means one request at a time per connection, which has the same blocking effect per connection.
#Six connections, and the hacks they caused
To fetch in parallel anyway, browsers open up to six connections per host. Each one pays its own handshakes and slow start, and six is still not many for a page with 80 files. Web developers invented workarounds that you will still find in old code:
| HTTP/1.1 workaround | What it does | Under HTTP/2 and HTTP/3 |
|---|---|---|
| Domain sharding | Spread files over img1.example.com, img2… to get 6 connections per name | Harmful: extra DNS lookups and handshakes, defeats multiplexing (unless coalescing rescues it) |
| Concatenation / bundling | Merge all JS or CSS into one big file | Less needed: small files are cheap; one change no longer invalidates one giant cached file |
| Image sprites | Many icons in one image, cut out with CSS | Mostly unnecessary |
| Inlining | Put CSS or small images directly in the HTML | Still useful for tiny critical CSS, but defeats caching |
#The Host header and virtual hosting
HTTP/1.1 made the Host header mandatory: Host: shop.example.com. With it, one IP address can serve many domains (name-based virtual hosting): the server reads Host and picks the right site. Without it every website needed its own IPv4 address, which the world could never have afforded. A request without Host gets 400 Bad Request. With HTTPS the name travels even earlier, as SNI in the TLS ClientHello, so the server can choose the right certificate before it reads any HTTP. HTTP/2 and HTTP/3 carry the same value as the :authority pseudo-header.
#Chunked transfer encoding
When a server streams something whose size it does not know yet (a report built row by row, a server-rendered page, an AI answer), it cannot send Content-Length. HTTP/1.1 added Transfer-Encoding: chunked: each chunk is preceded by its size in hexadecimal, and a zero-size chunk ends the body, optionally followed by trailer headers. The connection stays usable afterwards.
HTTP/1.1 200 OK
Content-Type: text/plain
Transfer-Encoding: chunked
1a
first twenty-six bytes....
400
…1024 more bytes…
0
HTTP/1.1 also brought 100 Continue (ask before uploading a large body), range requests (resume a download, seek in a video) and a full caching model with Cache-Control and ETag (lesson 2.4).
#Where HTTP/1.1 hurts in production
- Keep-alive races. A load balancer reuses a pooled backend connection at the moment the backend closes it as idle, and the user gets a random
502. Rule: the backend's keep-alive timeout must be longer than the proxy's idle timeout. - Request smuggling. A text protocol has more than one way to say where a body ends (
Content-Length,Transfer-Encoding). If a front proxy and a backend disagree, an attacker hides a second request inside the first. Fixes: reject messages with both headers, normalise at the edge, use HTTP/2 to backends. - Head-of-line blocking per connection, which pushes clients to open many connections, which costs servers memory and file descriptors.
HTTP/1.1 on the wire: persistence, Host, chunks and their traps
A browser, a load balancer (a reverse proxy) and a backend server. Each scenario is one HTTP/1.1 feature, or one way it fails in production.
Scenario
Two requests over one persistent connection, then a polite close.
The browser pays 2 round trips once to set up the connection.
#HTTP/2: one connection, many streams
HTTP/2 (RFC 7540 in 2015, now RFC 9113) grew out of Google's SPDY experiment. It keeps every method, status code and header, and replaces the text wire format.
#Binary framing
HTTP/2 messages are cut into binary frames. Every frame starts with the same 9-byte header:
+-----------------------------------------------+
| Length (24 bits) |
+---------------+---------------+---------------+
| Type (8) | Flags (8) |
+-+-------------+---------------+-------------------------------+
|R| Stream Identifier (31 bits) |
+=+=============================================================+
| Frame Payload (0 … Length bytes) |
+---------------------------------------------------------------+
| Frame type | What it carries |
|---|---|
HEADERS | A request's or response's compressed headers; opens a stream |
DATA | Body bytes; the END_STREAM flag marks the last one |
SETTINGS | Each side's limits: max concurrent streams, initial window, header table size, max frame size |
WINDOW_UPDATE | Flow-control credit for a stream or the whole connection |
RST_STREAM | Cancel one stream without touching the others |
GOAWAY | "No new streams on this connection; I finish up to stream N" |
PING | Liveness check and round-trip measurement |
PUSH_PROMISE | Server push (obsolete, see below) |
Binary framing is faster and unambiguous to parse: every frame states its length, so there is no guessing where a message ends and no smuggling through conflicting length headers. The price is that you can no longer read the protocol with telnet; you need curl -v, nghttp or Wireshark.
#Streams and multiplexing
A stream is one request/response exchange, numbered: odd IDs for client-initiated streams (1, 3, 5…), even IDs for server-initiated ones, 0 for the connection itself. Frames of different streams interleave on one TCP connection, and because each frame names its stream, the order between streams does not matter. That removes HTTP-level head-of-line blocking: a slow response no longer blocks fast ones.
Consequences that matter for design:
- One connection per origin is enough. It does one handshake and builds one well-warmed congestion window instead of six cold ones.
- The server limits concurrent streams (
SETTINGS_MAX_CONCURRENT_STREAMS, often 100 to 250). Extra requests queue on the client. - Cancelling is cheap:
RST_STREAMstops one response. In HTTP/1.1 you had to close the connection.
#Flow control per stream
TCP already has flow control, so why another layer? Because many streams share one TCP connection: if the video player stops reading stream 9, TCP's receive window would fill and every stream would stop. HTTP/2 therefore gives each stream and the whole connection a send window (default 65,535 bytes), refilled with WINDOW_UPDATE as the receiver consumes data. A window smaller than the bandwidth-delay product caps throughput (lesson 1.10), which is why browsers advertise windows of several megabytes, and why a proxy left at the 64 KB default can make large downloads mysteriously slow.
#HPACK header compression
HTTP headers are repetitive: every request to a site carries the same :authority, user-agent, accept-* and often a large cookie. On a page with 80 requests that is tens of kilobytes of identical text, sent upstream, where bandwidth is smallest and the initial congestion window is only 10 segments. HPACK (RFC 7541) compresses them with three tools:
- A static table of 61 common header fields (
:method: GETis entry 2,:path: /is entry 4,:status: 200is entry 8). A match costs one byte. - A dynamic table, built by the connection itself: when a new header is sent as a literal, both sides append it. Next time it is one byte. The table is FIFO with a size limit (default 4,096 bytes; an entry costs name + value + 32 bytes), and the oldest entries are evicted.
- Huffman coding for literal strings, which saves roughly 20–30%.
HPACK was designed after the 2012 attacks that recovered cookies by watching how well attacker-controlled text compressed next to a secret (the reason TLS and SPDY compression were switched off). It only matches whole header values, and a never-indexed flag keeps secrets such as authorization out of every table on the path. Servers must also cap the decoded header size (SETTINGS_MAX_HEADER_LIST_SIZE), because a small compressed block can expand into megabytes.
HPACK: compressing headers with shared tables
Encoder and decoder keep identical tables: a fixed static table of 61 common headers, and a dynamic table filled by this connection’s own headers. Repeats become one-byte indexes.
Scenario
Header block, encoded
:method: GET
static 2 → 1 byte
:scheme: https
static 7 → 1 byte
:path: /
static 4 → 1 byte
Dynamic table (both sides keep the same copy)
| Index | Header | Size (bytes) |
|---|
:method GET, :scheme https and :path / are entries 2, 7 and 4 of the static table, so each is sent as a single byte. No text at all.
#Priorities: from dependency trees to RFC 9218
With everything on one connection the server must decide what to send first: render-blocking CSS before a below-the-fold photo. HTTP/2 shipped a complex dependency tree with weights. Few servers implemented it well, and browsers each built different trees, so RFC 9113 deprecated it. The replacement, Extensible Priorities (RFC 9218), is simple and works for HTTP/3 too: a Priority header (and a PRIORITY_UPDATE frame) with an urgency from 0 (highest) to 7 (default 3) and an incremental flag for things such as progressive images, for example Priority: u=0 for CSS and Priority: u=5, i for an image.
#Connection coalescing
If www.example.com and static.example.com resolve to the same IP address and the server's certificate covers both names, a browser may send requests for both over one HTTP/2 connection. This is connection coalescing: it undoes domain sharding for free. When it goes wrong (two names share an IP and a wildcard certificate but are served by different backends), the server answers 421 Misdirected Request and the browser opens a separate connection.
#TLS: required by browsers, not by the standard
The notes say HTTP/2 makes TLS mandatory. The specification does not: it defines h2 (over TLS) and h2c (cleartext). Browsers only implement h2 over TLS. Cleartext h2c is used inside data centres, for example between a proxy and a gRPC backend, by "prior knowledge" (the client simply starts with the HTTP/2 preface). The old way of upgrading an HTTP/1.1 connection to h2c was deprecated in RFC 9113.
#What HTTP/2 did not fix: TCP head-of-line blocking
All streams are still bytes in one TCP byte stream. If one TCP segment is lost, TCP holds back everything after it, including frames of streams that had nothing to do with the lost data, until the retransmission arrives. On a clean network this is invisible; at 1–2% packet loss (crowded Wi-Fi, mobile) one connection stalls on almost every page, and six HTTP/1.1 connections, which stall independently, can actually be faster. That is the problem HTTP/3 was built to solve.
Inside an HTTP/2 connection: frames, streams, flow control
Every HTTP/2 frame has a 9-byte header: length, type, flags and a stream ID. Stream 0 is the connection itself; client streams are odd.
Scenario
How an HTTP/2 connection starts and its first request.
ALPN inside the TLS handshake settles the protocol: both sides now know they speak h2.
#HTTP/3: HTTP over QUIC
HTTP/3 (RFC 9114, June 2022) maps HTTP onto QUIC (RFC 9000, 2021), the UDP-based transport you met in lesson 1.9. Google built the first version of QUIC around 2012 and called it "Quick UDP Internet Connections"; the IETF version is a different, standardised protocol and QUIC is now simply a name, not an acronym.
#What QUIC gives HTTP
- Independent streams. QUIC itself provides streams; data is ordered per stream, so a lost packet delays only the stream whose data it carried. TCP-level HoL blocking is gone.
- Faster setup. Transport and TLS 1.3 handshakes are combined: 1 RTT for a new connection instead of 2 (TCP + TLS 1.3), and 0 RTT when resuming with a session ticket.
- 0-RTT has a catch: replay. Early data can be recorded and resent by an attacker, so servers accept it only for safe, idempotent requests, reject it otherwise, or answer
425 Too Early; a CDN passesEarly-Data: 1to the origin so it can decide. - Connection migration. A connection is identified by connection IDs, not by IP address and port, so it survives a change of network.
- Encrypted transport headers and user-space implementation: middleboxes cannot ossify it, and new congestion control ships with a browser update.
Because QUIC provides streams and flow control, HTTP/3 is thinner than HTTP/2: each request uses one client-initiated bidirectional QUIC stream (IDs 0, 4, 8…); HEADERS, DATA, SETTINGS and GOAWAY frames remain, while WINDOW_UPDATE, RST_STREAM and PING are gone because QUIC does those jobs itself.
#QPACK: compression without cross-stream blocking
HPACK assumes header blocks are decoded in the order they were encoded, which TCP guaranteed. QUIC streams arrive in any order, so a request referring to "dynamic entry 62" might arrive before the request that created entry 62. QPACK (RFC 9204) fixes this: table insertions travel on a dedicated encoder stream, each header block declares how many insertions it needs (its Required Insert Count), and only a stream whose entry is missing waits. The decoder limits how many streams may block (SETTINGS_QPACK_BLOCKED_STREAMS, default 0), and an encoder that must never block refers only to acknowledged entries or sends literals. The static table has 99 entries.
#Connection migration
The details that make it work, and the ways it fails:
- Path validation. Before trusting a new address the server sends
PATH_CHALLENGEand waits forPATH_RESPONSE, and until then sends at most three times what it received, so nobody can use migration to aim traffic at a victim. - New connection IDs per path, issued in advance, so an observer cannot link your Wi-Fi and mobile traffic.
- NAT rebinding, a home router silently changing your public port after an idle period, is handled the same way. For TCP it is fatal.
- Load balancers must route by connection ID, not by the address 4-tuple. Servers encode their own ID inside the connection IDs they issue (the QUIC-LB idea); a load balancer that hashes the 4-tuple sends the migrated connection to a server that does not know it, which answers with a stateless reset.
- Only the client migrates actively, and a server may forbid it.
Connection migration: walking out of Wi-Fi range mid-download
A phone streams a video over Wi-Fi, then the user walks outside and the phone switches to mobile data. Its address changes. What happens to the connection?
Scenario
Packet as the server side sees it
source
203.0.113.5:40001
destination
203.0.113.10:443
Over Wi-Fi the server sees the connection as 203.0.113.5:40001 → 203.0.113.10:443. A TCP connection is identified by exactly these four values.
#Finding HTTP/3: Alt-Svc and the HTTPS record
A browser cannot just try UDP on every site, so it must learn that HTTP/3 exists:
- Alt-Svc. The first visit uses TCP (HTTP/2), and the response carries
Alt-Svc: h3=":443"; ma=86400: "this origin also speaks HTTP/3 on UDP 443, remember for a day." The next connection uses HTTP/3. - DNS HTTPS record (type 65, RFC 9460). The site publishes
example.com. HTTPS 1 . alpn="h3,h2" ipv4hint=…; browsers query it in parallel with A/AAAA, so even the first visit can use HTTP/3 (the record can also carry the key for Encrypted Client Hello).
And when UDP is blocked (many corporate and hotel networks drop UDP 443), the browser does not wait for a timeout: it races a TCP connection, uses HTTP/2, and marks HTTP/3 as broken for that network for a while. Every HTTP/3 deployment therefore also serves HTTP/2 and HTTP/1.1 over TCP.
#What HTTP/3 costs
HTTP/3 is not free. Encrypting and acknowledging every packet in user space costs more CPU per byte than kernel TCP with hardware offloads (the gap has narrowed with batching and segmentation offload). Some networks rate-limit or block UDP. Load balancers, firewalls and DDoS protection need QUIC awareness. Observability is harder, because headers that used to be readable on the wire are encrypted. And the latency gain is real but bounded: HTTP/3 saves handshake round trips and loss stalls; it cannot beat the speed of light between user and server, so "HTTP/3 makes latency almost zero" (a phrase in the notes) is an exaggeration. The gains are largest on high-latency, lossy networks: mobile users far from the server.
#Choosing the version: ALPN
On TCP port 443, HTTP/1.1 and HTTP/2 look identical until the first bytes, so the version is agreed inside the TLS handshake with ALPN (Application-Layer Protocol Negotiation):
- The ClientHello lists protocols in order of preference:
h2, http/1.1. - The server picks one and returns it (in TLS 1.3, inside the encrypted part of its reply).
- No answer means HTTP/1.1. If the server supports none of the offered protocols it can abort with a
no_application_protocolalert.
This costs no extra round trip. HTTP/3 is identified by ALPN h3 inside the QUIC handshake. A few practical consequences:
- Each hop negotiates separately. Browser → CDN might be HTTP/3, CDN → load balancer HTTP/2, load balancer → backend HTTP/1.1. The version you see in the browser says nothing about the backend.
- gRPC requires HTTP/2. A TLS-terminating proxy that only speaks HTTP/1.1 to the backend, or does not offer
h2to the client, breaks gRPC while web browsing keeps working. - Cleartext HTTP/2 (h2c) has no TLS and therefore no ALPN; it is used by prior knowledge inside private networks.
Choosing the version: ALPN, Alt-Svc, the HTTPS record and fallback
HTTP/1.1 and HTTP/2 share port 443 over TCP, so the version is chosen inside TLS. HTTP/3 lives on UDP, so the browser must first learn that it exists.
Scenario
A brand-new visit over TCP. The server also advertises HTTP/3 for next time.
The usual address lookup.
#Server push, and why it died
HTTP/2 let a server push responses the client had not asked for yet: send index.html and, with a PUSH_PROMISE, also style.css, saving a round trip. It failed in practice:
- The server cannot know what the browser already has in its cache. Pushing a cached file wastes bandwidth that the HTML needed.
- By the time the browser cancels with
RST_STREAM, most of the bytes are already in flight. - It was hard to configure, interacted badly with priorities, and measurements showed it rarely helped.
Chrome removed push in 2022 (version 106), Firefox in 2024, servers such as Nginx dropped their support, and HTTP/3 push was never implemented by browsers. (The notes still list push as an HTTP/2 feature; it is effectively dead.)
What replaced it gives the decision back to the browser:
preloadandpreconnecthints (<link rel="preload" href="/style.css" as="style">) tell the browser early what it will need; the browser checks its cache first.103 Early Hints(RFC 8297) sends those hints before the real response: while the origin spends 400 ms building a page, the CDN immediately answers103withLink: </style.css>; rel=preload, and the browser uses the idle time to fetch CSS and open connections. Sites report a few hundred milliseconds off Largest Contentful Paint. Browsers act on it over HTTP/2 and HTTP/3; if it is ignored or unused, nothing breaks.
Getting critical files early: push (dead) vs 103 Early Hints
The origin needs 400 ms to build the product page. Can the browser use that time to fetch the CSS it will certainly need?
Scenario
The baseline: the browser learns about style.css only after the HTML arrives.
The browser asks for the page.
#The four versions side by side
| Property | HTTP/1.0 | HTTP/1.1 | HTTP/2 | HTTP/3 |
|---|---|---|---|---|
| Transport | TCP | TCP | TCP | QUIC over UDP |
| Format | Text | Text | Binary frames | Binary frames |
| Connection reuse | No (opt-in keep-alive extension) | Yes, persistent by default | Yes, multiplexed | Yes, multiplexed |
| Requests in parallel on one connection | No | No (pipelining unused) | Yes, streams | Yes, independent streams |
| Head-of-line blocking | Queueing for the few connections | HTTP level, per connection | TCP level, whole connection | Only within the stream that lost data |
| Header compression | None | None | HPACK | QPACK |
| Encryption | Optional (HTTP or HTTPS) | Optional | TLS in browsers (h2c allowed by spec) | TLS 1.3 always, built in |
| Setup before first request (new connection) | TCP + TLS: 2–3 RTT | 2–3 RTT, then reused | 2 RTT (TLS 1.3), then reused | 1 RTT, 0 on resumption |
| Survives IP change | No | No | No | Yes (connection migration) |
| Default port | 80 / 443 | 80 / 443 | 443 (TCP) | 443 (UDP) |
#Adoption today
As of 2026 every major browser supports HTTP/2 and HTTP/3, and large CDNs (Cloudflare, Google, Akamai, Fastly, CloudFront) serve HTTP/3 by default or with one switch. Public measurements such as Cloudflare Radar and the HTTP Archive show the same broad picture year after year: HTTP/2 carries the largest share of browser traffic, HTTP/3 a large and growing minority (roughly a fifth to a third of requests), and HTTP/1.1 is mostly bots, scripts, API clients, old devices and server-to-server hops. The exact shares move every year, so check those sources when you need a number. Behind the edge the picture is different: CDN-to-origin and load-balancer-to-backend hops are still very often HTTP/1.1, and internal service calls are HTTP/2 (gRPC).
#HTTP versions in system design
- Terminate the newest version at the edge. Users on mobile and Wi-Fi gain most from HTTP/3 and HTTP/2; enable them on the CDN or load balancer and keep TCP for fallback.
- Choose backend protocols separately. HTTP/1.1 with keep-alive pools is fine for many backends; HTTP/2 helps when one client sends many concurrent requests (gRPC, API gateways) and is safer against request smuggling.
- Long-lived HTTP/2 connections defeat connection-level load balancing. A layer 4 balancer spreads connections; one HTTP/2 client may send all its traffic over one connection to one backend for hours. Use a layer 7 (request-level) balancer for gRPC, cap connection age, and drain with
GOAWAY. - Watch the limits.
SETTINGS_MAX_CONCURRENT_STREAMSqueues requests on the client; small flow-control windows cap throughput on long paths; idle timeouts must be ordered (client < load balancer < backend keep-alive). - Remove HTTP/1.1 hacks after moving to HTTP/2: domain sharding and giant bundles now hurt.
- Protect against protocol-level abuse. The HTTP/2 "rapid reset" attack (October 2023, a record 398 million requests per second against Google) abused cheap stream cancellation; keep servers patched and limit resets and streams per connection.
- Plan QUIC for your infrastructure: UDP 443 open in firewalls and security groups, a load balancer that routes by connection ID if you want migration, and metrics that do not depend on reading packets.
| Symptom | Likely cause | Look at |
|---|---|---|
Random 502 from the load balancer at low traffic | Backend closes idle keep-alive connections before the proxy does | Keep-alive and idle timeouts on both sides |
| Page loads fine on office Wi-Fi, stalls on mobile with HTTP/2 | TCP head-of-line blocking under packet loss | Enable HTTP/3; packet loss metrics |
| HTTP/3 never used although the server supports it | UDP 443 blocked, or no Alt-Svc / HTTPS record | Firewall rules, response headers, dig HTTPS |
| gRPC fails behind a proxy, browsers work | Proxy does not offer h2 or speaks HTTP/1.1 upstream | ALPN on both sides of the proxy |
| One backend hot, others idle, with gRPC clients | Connection-level balancing of long-lived HTTP/2 connections | Layer 7 balancing, max connection age |
| Large downloads slow only through one proxy | HTTP/2 flow-control window left at 64 KB | Proxy's initial window settings |
421 Misdirected Request | Connection coalescing sent a host to a backend that does not serve it | Certificates and IPs shared between hosts |
Self-checkQuestion
A page loads 40 small files from one host over HTTP/2. On a mobile network with 2% packet loss users report that the whole page freezes for a moment several times. What is the most precise explanation?
#HTTP among the other application protocols
HTTP is one of many application-layer protocols. The application layer is the seventh and top layer of the OSI model, the one closest to the user: it provides network services to applications. When you visit a website, send email, download a file, look up a name or log in to a remote server, an application-layer protocol is doing the work. An important detail: the application layer is not the application software itself (not Chrome, not Outlook) but the protocols and services those programs use to talk over the network. Its unit of data (PDU) is simply called data or a message.
What the layer provides, with the protocols that do each job:
| Function | What it means | Protocols |
|---|---|---|
| Network services | Give applications access to the network | All of the below |
| Web communication | Browsers and servers exchange pages and API data | HTTP, HTTPS |
| File transfer | Send and receive files | FTP, FTPS, SFTP |
| Send, relay and read mail | SMTP, IMAP, POP3 | |
| Name resolution | Turn names into IP addresses | DNS (also DoT, DoH, DoQ) |
| Remote access | Manage a remote computer or server | SSH (Telnet, obsolete) |
| Resource sharing | Use files and printers over the network | SMB, NFS |
| Network management | Monitor and manage devices | SNMP |
| Time and media | Synchronise clocks; carry live audio and video | NTP, RTP |
#Ports worth knowing
| Protocol | Port | Transport | Notes |
|---|---|---|---|
| HTTP | 80 | TCP | Cleartext; today mostly redirects to HTTPS |
| HTTPS (HTTP/1.1, HTTP/2) | 443 | TCP | HTTP over TLS: confidentiality, integrity, server authentication |
| HTTPS (HTTP/3) | 443 | UDP | QUIC |
| FTP | 21 control, 20 data (active) | TCP | Passive mode uses a high data port; no encryption |
| FTPS | 21 (explicit TLS) or 990 (implicit) | TCP | FTP with TLS |
SSH, SFTP, scp | 22 | TCP | SFTP runs inside SSH |
| Telnet | 23 | TCP | Unencrypted remote terminal |
| SMTP relay | 25 | TCP | Server to server |
| SMTP submission | 587 (STARTTLS), 465 (TLS) | TCP | Mail app to its own provider, with login |
| DNS | 53 | UDP, and TCP for large answers and zone transfers | Lesson 1.8 |
| DNS over TLS / over QUIC | 853 | TCP / UDP | Encrypted DNS |
| DNS over HTTPS | 443 | TCP or UDP | Looks like any HTTPS traffic |
| DHCP | 67 server, 68 client | UDP | Lesson 1.6 |
| POP3 | 110, 995 (TLS) | TCP | Download mail |
| IMAP | 143, 993 (TLS) | TCP | Read mail on the server |
| NTP | 123 | UDP | Clock synchronisation |
| SNMP | 161 queries, 162 traps | UDP | Device monitoring |
| SMB | 445 | TCP | Windows file and printer sharing |
| RTP | Dynamic high ports | UDP | Live audio and video; set up by SIP or WebRTC |
#DNS and DHCP, briefly
Both were taught in Chapter 1, so only the summary here. DNS resolves a domain name such as www.google.com to an IP address, normally over UDP 53 and over TCP 53 for large responses and zone transfers; its record types (A, AAAA, CNAME, MX, NS, TXT, PTR, SOA, SRV) and caching are in lesson 1.8. What the notes leave out: DNS can also be encrypted, as DNS over TLS (DoT, TCP 853), DNS over HTTPS (DoH, 443) and DNS over QUIC (DoQ, UDP 853). DHCP gives a device its IP address, subnet mask, default gateway, DNS servers and lease time through the four DORA steps (Discover, Offer, Request, Acknowledge) on UDP 67/68; see lesson 1.6.
#Email: SMTP, IMAP and POP3
Email uses three protocols and DNS, and the roles matter more than the names:
- Submission. Alice's mail app (the MUA, mail user agent) hands the message to her own provider's submission server (MSA) with SMTP on port 587 (upgraded to TLS with
STARTTLS) or 465 (TLS from the first byte, recommended since RFC 8314). Submission always requires a login. - Relay. The provider's mail server (MTA) looks up the recipient domain's MX records in DNS, picks the lowest priority number, and delivers with SMTP on port 25, server to server, using TLS opportunistically. SMTP is store-and-forward: a server that answers
250 OKtakes responsibility, and if the next hop is down the message waits in a queue and is retried for days. - Retrieval. Bob reads it with IMAP (143, or 993 with TLS), which keeps mail on the server and syncs folders and read status across devices, or POP3 (110 / 995), which downloads it, usually to a single device.
$ openssl s_client -starttls smtp -connect smtp.example.com:587 -quiet
250-smtp.example.com
EHLO laptop.example.com
AUTH PLAIN … (after TLS: log in)
MAIL FROM:<alice@example.com>
RCPT TO:<bob@example.net>
DATA
Subject: Hello
…
.
250 2.0.0 Ok: queued
SMTP never checked who the sender was: anyone can write From: ceo@example.com. Three DNS-published mechanisms fix that, and since 2024 Gmail and Yahoo require all three from bulk senders:
| Mechanism | Published as | What the receiver checks |
|---|---|---|
| SPF | TXT record listing allowed sending IPs | Is the connecting server allowed to send for this domain? |
| DKIM | Public key in a TXT record under selector._domainkey | Is the signature on the message valid, so it was not altered? |
| DMARC | TXT record at _dmarc.example.com with a policy (none, quarantine, reject) | Does SPF or DKIM pass and align with the visible From domain? What to do if not, and where to send reports |
Two production facts that trip up developers: outbound port 25 is blocked by home ISPs and most clouds (AWS EC2 by default), so applications send through a provider such as Amazon SES on 587/465; and a domain without an MX record makes senders fall back to its A record, then retry for days, unless it publishes a null MX (MX 0 .) to say "I accept no mail".
Email delivery: submission, MX lookup, relay, IMAP
alice@example.com writes to bob@example.net. Four protocols and two DNS lookups carry one message; each scenario breaks a different link.
Scenario
Protocols on this path
| Hop | Protocol and port |
|---|---|
| app → own provider | SMTP submission 587 (STARTTLS) or 465 (TLS) |
| provider → recipient’s MX | SMTP 25 |
| mailbox → reader | IMAP 993 (or POP3 995) |
Alice’s app submits the message to her provider on port 587, upgrades to TLS with STARTTLS and logs in (or connects to 465 with TLS from the first byte). Submission always requires authentication.
#Remote access: SSH instead of Telnet
Telnet (TCP 23) gives a remote terminal but sends everything, passwords included, in plain text; anyone on the path can read them. SSH (TCP 22) replaced it: key exchange and encryption, host keys that prove the server's identity (remembered in known_hosts on first use, and a loud warning if they change), and login with key pairs instead of passwords. One SSH connection can also carry file transfer (SFTP, scp), port forwarding and tunnels. Telnet survives on old network gear and as a quick "is this TCP port open?" check (telnet host 443), which lesson 1.10 used.
#File transfer: FTP, FTPS, SFTP and SMB
FTP (1971, ports 21 and 20) uses two connections: a control connection on 21 and a separate data connection. In active mode the server connects back to the client from port 20, which NAT and client firewalls block; in passive mode the client connects to a high port the server announces, which works through NAT but forces the server's firewall to open a range. FTP itself has no encryption. The secure alternatives are different protocols:
- FTPS is FTP with TLS (explicit on 21 or implicit on 990): still two connections, now encrypted, which makes firewalls and NAT even harder.
- SFTP is not FTP: it is a file protocol that runs inside SSH on port 22, one connection, key-based authentication. Prefer it.
- SMB (TCP 445) shares files and printers in Windows networks (and with Samba on Linux). Never expose 445 to the internet: old SMB version 1 was the entry point of the 2017 ransomware worms; it is disabled by default today.
Browsers removed FTP support in 2021. For new systems, file exchange is SFTP or, more often, HTTPS uploads to object storage with pre-signed URLs.
Remote access and file transfer: Telnet, SSH, FTP, SFTP
An admin on café Wi-Fi manages a server. Someone else on the same Wi-Fi is capturing traffic, and the café router does NAT.
Scenario
Telnet (1969, port 23) gives a remote terminal over plain TCP.
#Time and monitoring: NTP and SNMP
NTP (UDP 123) keeps clocks synchronised. A client sends a timestamped request; from the four timestamps (sent, received by the server, answered, received back) it computes both the network delay and its clock offset, then slews the clock gently rather than jumping it. Over the internet it keeps clocks within a few milliseconds; inside a data centre, much closer (PTP goes further for finance and telecom). Servers form strata: stratum 1 is attached to an atomic or satellite clock, stratum 2 syncs from stratum 1, and so on. Why system designers care: clock skew breaks TLS certificate validation ("not yet valid"), JWT and cookie expiry, Kerberos (5 minutes of tolerance), and the ordering of logs during an incident. Monitor clock offset, use your cloud's local time service, and use NTS where an attacker could shift your clock. (Chapter 38 covers why distributed systems cannot rely on wall clocks for ordering at all.)
SNMP (UDP 161 for queries, 162 for traps) lets a monitoring system read counters and status from routers, switches, printers and servers. Values live in a MIB and are named by numeric object identifiers. The manager polls (for example every 60 seconds) and computes rates from counter differences; devices send traps when something happens, but traps are unacknowledged UDP and can be lost, so polling stays the safety net. SNMP versions 1 and 2c authenticate with a plain-text "community string" (often left as public); use SNMPv3 with authentication and encryption and never expose it to the internet.
Keeping time and watching devices: NTP and SNMP
Two quiet UDP protocols that every production network depends on.
Scenario
The client stamps the request with its own clock (t1).
#RTP: real-time media
RTP carries live audio and video, usually over UDP, with sequence numbers and timestamps so the receiver can reorder packets, measure jitter and play smoothly; a late packet is simply skipped. RTP does not set up calls: SIP or WebRTC signalling does that, and WebRTC always encrypts media as SRTP. Lesson 2.6 covers WebRTC.
#A real example: opening YouTube
#When the application layer fails
| Symptom | Typical cause | First tool |
|---|---|---|
| Website does not open | DNS failure, TLS error, server down, HTTP 5xx | dig, curl -v, browser developer tools |
| DNS resolution failure | Wrong record, resolver down, firewall blocking 53 | dig, nslookup |
| Email not sent or not received | Port 25 blocked, missing MX, SPF/DKIM/DMARC failure, queue | dig MX, openssl s_client -starttls smtp, bounce messages |
| FTP connects but listing hangs | Active mode blocked by NAT, passive range closed | ftp in passive mode, firewall rules |
| API errors | 4xx from the client's request, 5xx from the server (lesson 2.2) | curl -v, server logs |
| Authentication failure | Wrong credentials, expired token, clock skew, changed SSH host key | ssh -v, token expiry, chronyc tracking |
| Application server unavailable | Crashed process, load balancer health check failing | Health endpoint, curl, logs |
#Try it yourself
# Which HTTP version did you get? (-I: headers only)
curl -sI --http1.1 https://www.example.com | head -1 # HTTP/1.1 200 OK
curl -sI --http2 https://www.example.com | head -1 # HTTP/2 200
curl -sI --http3 https://cloudflare.com | head -1 # HTTP/3 200 (needs a curl built with HTTP/3)
# Watch ALPN and the version in a verbose request
curl -sv -o /dev/null https://www.google.com 2>&1 | grep -Ei 'alpn|using http'
# Does the site advertise HTTP/3?
curl -sI https://www.google.com | grep -i alt-svc # alt-svc: h3=":443"; ma=2592000
dig +short HTTPS cloudflare.com # 1 . alpn="h3,h2" ipv4hint=…
# ALPN by hand: offer only h2 and see what the server picks
openssl s_client -connect www.example.com:443 -alpn h2 </dev/null 2>/dev/null | grep -i alpn
# HTTP/2 frames up close (nghttp2 client)
nghttp -nv https://nghttp2.org | head -40
# Chunked response: see the raw chunk sizes
curl -s --raw --http1.1 https://httpbin.org/stream/3 | head -6
# Email: MX records, SPF/DMARC policies, and a submission server
dig +short MX gmail.com
dig +short TXT _dmarc.gmail.com
openssl s_client -starttls smtp -connect smtp.gmail.com:587 -quiet </dev/null
# Remote access and time
ssh -v user@server.example.com 2>&1 | grep -i 'host key'
chronyc tracking # or: timedatectl timesync-status / w32tm /query /status on Windows
snmpwalk -v3 -l authPriv -u monitor -a SHA -A '…' -x AES -X '…' 192.0.2.1 IF-MIB::ifInOctets
In the browser, open developer tools → Network, right-click a column header and enable Protocol: you will see h2, h3 or http/1.1 per request. Reload a few times; a site often shows h2 on the first load and h3 after its Alt-Svc header has been seen. In Wireshark, the filter http2 shows frames of a decrypted HTTP/2 capture (set SSLKEYLOGFILE in the browser to let Wireshark decrypt), and quic shows QUIC packets.
#Summary
Key takeaways
- HTTP's semantics (methods, status codes, headers) are the same in every version; each version changed only the wire format to reuse connections better and reduce head-of-line blocking.
- HTTP/1.0: a new TCP connection per request, closed after the response; 50 images meant 50 handshakes.
- HTTP/1.1: persistent connections by default,
Hostheader (virtual hosting), chunked transfer, ranges. Pipelining forced in-order responses and was never enabled by browsers, so clients open 6 connections per host; watch for keep-alive races and request smuggling. - HTTP/2: binary frames with a 9-byte header, streams multiplexed on one connection, per-stream flow control, HPACK (static table, dynamic table, Huffman), RFC 9218 priorities, connection coalescing,
GOAWAY. HTTP-level HoL is gone, TCP-level HoL remains. TLS is required by browsers, not by the standard (h2c). - HTTP/3 over QUIC: independent streams, 1-RTT setup and 0-RTT resumption (replay risk: idempotent requests only,
425 Too Early), QPACK, connection migration via connection IDs (needs connection-ID-aware load balancers). Discovered through Alt-Svc or the DNS HTTPS record; falls back to TCP when UDP is blocked. - ALPN picks
h2orhttp/1.1inside the TLS handshake at no extra cost; each hop negotiates separately; gRPC needs h2. - Server push is dead (Chrome 2022, Firefox 2024); 103 Early Hints and
preloadreplace it. - Other application protocols: SMTP 587/465 submission and 25 relay via MX, IMAP 993 / POP3 995, SPF/DKIM/DMARC against spoofing; SSH 22 instead of Telnet 23; SFTP (inside SSH) instead of FTP 21/20; NTP 123 keeps clocks right; SNMP 161/162 monitors devices; SMB 445; RTP over UDP; DNS can be encrypted with DoT/DoH/DoQ.
Answer on your own first, then read the explanation, even when you got it right.
Self-checkQuestion
Why did HTTP/1.0 close the connection after every response?
Self-checkQuestion
Which statement about keep-alive is correct?
Self-checkQuestion
A client pipelines requests for /slow, /a.css and /b.js on one HTTP/1.1 connection. The server finishes /a.css first. What must it do?
Self-checkQuestion
Why do HTTP/1.1 browsers open about six connections per host?
Self-checkQuestion
One server at 203.0.113.10 hosts blog.example.com and shop.example.com over plain HTTP. How does it know which site a request is for?
Self-checkQuestion
When does a server use Transfer-Encoding: chunked instead of Content-Length?
Self-checkQuestion
A load balancer keeps idle backend connections for 60 seconds; the backend framework closes idle keep-alive connections after 5 seconds. Users see occasional random 502 errors, mostly at night. Which two changes fix it?
Choose 2
Self-checkQuestion
What makes HTTP request smuggling possible?
Self-checkQuestion
What is in every HTTP/2 frame header?
Self-checkQuestion
Which two statements about HTTP/2 streams are correct?
Choose 2
Self-checkQuestion
Why does HTTP/2 have its own flow control when TCP already has one?
Self-checkQuestion
An HTTP/2 connection's HPACK dynamic table already holds cookie, user-agent and :authority from the first request. Roughly what does the second request's cookie header cost?
Self-checkQuestion
Why should an authorization header carrying a bearer token be sent as a never-indexed literal in HPACK?
Self-checkQuestion
What do HTTP/2 connection coalescing and the 421 status code have to do with each other?
Self-checkQuestion
Does HTTP/2 require TLS?
Self-checkQuestion
Which statement about QUIC's name is correct today?
Self-checkQuestion
A packet carrying part of app.js (stream 8) is lost on an HTTP/3 connection while css, logo and photo are also downloading. What happens?
Self-checkQuestion
Why can a returning browser send its first HTTP/3 request with zero round trips of setup, and what restricts it?
Self-checkQuestion
Why does HTTP/3 use QPACK instead of HPACK?
Self-checkQuestion
A phone switches from Wi-Fi to mobile data during an HTTP/3 download. The connection survives on a test server but dies in production behind a load balancer. What is the most likely cause?
Self-checkQuestion
Which two mechanisms let a browser learn that a site supports HTTP/3?
Choose 2
Self-checkQuestion
An office firewall blocks UDP 443. What does a modern browser do on a site that advertises HTTP/3?
Self-checkQuestion
How do a browser and a server agree to use HTTP/2 on port 443, and what does it cost?
Self-checkQuestion
Browsers reach an API gateway over HTTP/2 without problems, but a gRPC service behind the same gateway fails with protocol errors. The gateway terminates TLS. What should you check first?
Self-checkQuestion
Why did HTTP/2 server push fail in practice?
Self-checkQuestion
A product page needs 400 ms of origin work. Which change lets the browser fetch the page's critical CSS during those 400 ms?
Self-checkQuestion
Which statement about HTTP/3 is not accurate?
Self-checkQuestion
A developer's script on a new cloud VM sends email directly to recipients' MX servers on port 25, and every connection times out. What is the right fix?
Self-checkQuestion
A spoofed email claiming to be from ceo@example.com reaches Bob's mail server. example.com publishes SPF, signs with DKIM and has a DMARC policy of p=reject. What happens?
Self-checkQuestion
Which two statements about mail retrieval and submission ports are correct?
Choose 2
Self-checkQuestion
An FTP client behind a home router logs in successfully, but every directory listing hangs. Switching which setting is most likely to help?
Self-checkQuestion
Which two statements about SSH, Telnet and SFTP are correct?
Choose 2
Self-checkQuestion
An NTP client sends at t1 = 0 ms, the server receives at t2 = 130 ms and replies at t3 = 131 ms (server clock), and the client receives at t4 = 41 ms (client clock). What are the round-trip delay and the client's clock offset?
Self-checkQuestion
After a firewall change, a fleet of servers stopped reaching their NTP servers weeks ago. Which problem are you most likely to see first?
Self-checkQuestion
A switch sends an SNMP trap when a link goes down, but the monitoring system never shows the alert. Which design would have caught the failure anyway?