Engineering Atlas › System Design Fundamentals

How HTTP and HTTPS work

14 min read · last reviewed 2026-10-17

Open in the interactive app: practice, quiz, mock interview →

In one minute

How it works

What a new connection costs

A fresh HTTPS request over TCP with TLS 1.3 needs three round trips before the first response byte: TCP handshake, TLS handshake, then the request. TLS 1.2 needs four. TLS 1.3 itself was republished in July 2026 as RFC 9846, which obsoletes RFC 8446 and the TLS 1.2 specification. QUIC folds transport and crypto setup into one round trip, so HTTP/3 needs two. A resumed TLS 1.3 or QUIC session can send the request in the first flight (0-RTT), at the price of replay risk.

At a 150 ms round trip, that is 450 ms versus 150 ms on a warm connection. The server pays too: a full handshake costs an ECDHE key exchange and a signature, roughly 0.1 to 0.3 ms of CPU, which adds up at 50,000 new connections per second.

Handshakes are also getting bigger. Hybrid post-quantum key agreement went mainstream fast: by the last week of October 2025 most human traffic to Cloudflare used it (Cloudflare). The common hybrid, X25519MLKEM768, sends a 1,216-byte client key share instead of X25519's 32 bytes (RFC 10024). The ClientHello no longer comfortably fits in one packet, and middleboxes that assume it does have broken connections in earlier experiments with large key shares, so test the whole path, not just the server.

Connection reuse

Keep-alive keeps the socket open, client pools keep warm connections per origin, and TLS session tickets let a returning client skip the certificate exchange. HTTP/2 adds connection coalescing: one connection can serve api.example.com and cdn.example.com if both resolve to the same address and the certificate covers both names; a server that cannot serve a name answers 421 Misdirected Request (RFC 9110).

HTTP/2: streams on one connection

HTTP/2 tags frames with a stream ID, so dozens of requests interleave on one connection. Each stream and the connection have flow-control windows, so one slow download need not block small responses. HPACK keeps a shared table of recently sent header fields: a 600-byte cookie becomes a one or two byte index after the first request. The original priority tree did not survive: RFC 9113 deprecated it, along with the cleartext Upgrade path, and HTTP/2 and HTTP/3 now signal urgency with the simpler Priority header field (RFC 9218).

The single connection is also the catch. All streams share one TCP byte stream, so one lost packet stalls every stream until it is retransmitted. And because the connection lives for minutes or hours, a layer 4 load balancer that balances connections stops balancing requests. This is the classic gRPC problem: 20 client pods with one HTTP/2 connection each to a 50-pod service keep at most 20 backends busy.

HTTP/3: streams in the transport

QUIC runs over UDP and implements streams, loss recovery, congestion control and TLS 1.3 in user space. A lost packet stalls only the streams whose data it carried. Connection IDs, not the address tuple, identify a connection, so a phone moving from Wi-Fi to cellular keeps it. QPACK replaces HPACK and sends table updates on separate streams so headers do not reintroduce head-of-line blocking (RFC 9204).

Clients discover HTTP/3 from an Alt-Svc: h3=":443" header on an earlier response, or from an HTTPS DNS record (RFC 9460) with alpn=h2,h3 before the first visit. They race QUIC against TCP and fall back silently, because some networks block UDP.

Where TLS terminates

Terminating at a CDN edge gives the shortest handshake and lets the edge cache, but the edge sees plaintext. Re-encrypting to services, often with mutual TLS, protects traffic inside the data centre. TLS passthrough (routing on SNI without decrypting) keeps keys only on the backend, but the proxy loses every layer 7 feature.

Certificates at fleet scale

ACME automates issuance: you prove control with an HTTP-01 challenge (a token at a well-known path) or DNS-01 (a TXT record, required for wildcards). Every public certificate lands in Certificate Transparency logs, which you can watch for certificates you did not request. In April 2025 the CA/Browser Forum voted to cut the maximum lifetime from 398 days to an eventual 47, and domain-validation reuse to an eventual 10 days (CA/B Forum SC-081v3). The steps are 200 days from 15 March 2026, 100 days from 15 March 2027 and 47 days from 15 March 2029 (DNSimple). Let's Encrypt goes further: its default profile moves from 90 to 64 days in February 2027 and to 45 days in February 2028 (Let's Encrypt). It also switched off its OCSP responders in August 2025 and relies on CRLs (Let's Encrypt). Manual renewal is no longer viable; renew from ACME Renewal Information or at about two-thirds of the lifetime, not on a fixed 60-day cron. HSTS tells browsers never to use plain HTTP for your domain.

Down the stack

Layer 1The contract

HTTP semantics (RFC 9110) are version-independent: methods, status codes and headers mean the same over HTTP/1.1, 2 and 3, so a proxy can speak HTTP/3 to the client and HTTP/1.1 to the origin. HTTPS adds that the peer holds a key certified for the requested hostname and that the stream is confidential and unmodified. HTTP/2 orders data only within a stream, and gives a precise retry contract: GOAWAY carries the last stream ID the server will process, so requests on higher IDs (or reset with REFUSED_STREAM) were not processed and can be retried, even POSTs. A lower-numbered request without a response is unknown, like any timeout.

Layer 2The mechanism

Every HTTP/2 frame starts with a 9-byte header: a 24-bit length, an 8-bit type (DATA, HEADERS, SETTINGS, WINDOW_UPDATE, RST_STREAM, GOAWAY), 8 bits of flags, and a 31-bit stream ID (odd for client streams). SETTINGS_MAX_CONCURRENT_STREAMS caps parallel requests (the RFC advises no fewer than 100, and 100 to 128 is common), and the initial flow-control window is 65,535 bytes per stream until WINDOW_UPDATE frames grow it. HPACK has a static table of 61 common fields and a dynamic table, 4,096 bytes by default, where each entry costs its name length plus value length plus 32 bytes of overhead; new entries take index 62 and the oldest are evicted first.

QUIC packet numbers are never reused, even for retransmitted data, so round-trip measurement is unambiguous; lost data is re-sent in new STREAM frames carrying the stream offset. ALPN tokens h2, http/1.1 and h3 travel in the TLS ClientHello and ServerHello.

Layer 3The machine

A new TCP connection may send about 10 segments (14 KB) before the first acknowledgement (RFC 6928), so the first response on a cold connection is slow regardless of bandwidth. On HTTP/2 over a path with 1% loss, each loss stalls all streams for at least one round trip; QUIC stalls only the affected stream. QUIC costs more CPU per byte because user space encrypts each packet and makes a syscall per datagram; UDP segmentation offload (UDP_SEGMENT) and sendmmsg close much of that gap. Each open connection holds socket buffers plus TLS state, tens of kilobytes, so a million idle keep-alive connections cost gigabytes of memory.

Why it's built this way

  1. Latency is bounded by the speed of light, so every version has attacked round trips: keep-alive, multiplexing, then merged handshakes and 0-RTT.
  2. Multiplexing over one TCP connection made a single lost packet stall everything.
  3. TCP lives in kernels and middleboxes that update over decades, so the fix moved to UDP and user space, where it ships with the browser.
  4. QUIC encrypts nearly all its headers so middleboxes cannot depend on them and freeze the protocol again.
  5. Short certificate lifetimes limit a leaked key's damage without relying on revocation, which clients check unreliably.

Diagram

sequenceDiagram
  participant C as Client
  participant D as DNS
  participant E as "Edge proxy"
  participant O as Origin
  C->>D: HTTPS record for api.example.com?
  D-->>C: alpn=h2,h3, ipv4hint
  C->>E: QUIC Initial + TLS ClientHello (ALPN h3)
  E-->>C: ServerHello, cert, Finished (1 RTT)
  C->>E: HTTP/3 GET /v1/feed (stream 0)
  E->>O: GET /v1/feed on pooled HTTP/2 connection
  O-->>E: 200 (stream 41)
  E-->>C: 200 on stream 0
  Note over C,E: Next visit sends 0-RTT GET with session ticket

Worked example

A mobile API serves 50,000 requests per second from us-east. Users in Southeast Asia see 220 ms round trips. The app makes about 8 calls per session start.

Without reuse, each call pays TCP plus TLS 1.3 plus the request, about 660 ms, and the origin runs 50,000 handshakes per second at about 0.2 ms CPU each: 10 cores on handshakes alone. With one HTTP/2 connection per session, the first call costs 660 ms, the other seven run in parallel in one more round trip, and handshakes drop to about 6,000 per second.

An edge in Singapore at 20 ms finishes TCP and TLS in 40 ms and keeps warm HTTP/2 connections to us-east, so only the request pays the 220 ms trip. Session start drops from about 1.3 seconds to about 300 ms, and origin handshakes fall to a few per second.

The trap: the origin sits behind a layer 4 balancer with 60 backends, and 12 edge proxies hold 2 connections each. Only 24 backends get traffic. Fix it with more connections per proxy, a maximum connection age (say 5 minutes, ending with GOAWAY), or a per-request layer 7 balancer.

In the wild

Trade-offs

OptionStrengthsWeaknessesChoose when
HTTP/1.1 with keep-aliveSimple, every tool and proxy understands itOne request in flight per connection, many socketsInternal hops through older proxies, debugging
HTTP/2Multiplexing, HPACK, one connection per originTCP head-of-line blocking, connection-level imbalanceDefault for browsers, gRPC, edge to origin
HTTP/3 (QUIC)1-RTT setup, no transport head-of-line blocking, connection migrationHigher CPU, UDP blocked on some networks, weaker toolingMobile and lossy last-mile links
Terminate TLS at edge onlyFastest handshake, caching, WAFPlaintext inside your networkStatic or low-sensitivity traffic
Re-encrypt or mTLS to backendsEncryption and identity on every hopCertificate rotation for every service, CPURegulated data, zero-trust networks
0-RTT resumptionSaves a full round tripEarly data can be replayedIdempotent GETs only

Pitfalls in production

Leadership lens

Check yourself

  1. A server sends GOAWAY with last-stream-ID 41 while a client has streams 39, 43 and 45 in flight. Which can be retried safely, including POSTs?

    Show answer

    Streams 43 and 45, because the server has promised it never processed streams above 41. Stream 39 may or may not have been processed and needs idempotency to retry.

  2. Why can switching a gRPC client from HTTP/1.1 to HTTP/2 make backend load more uneven?

    Show answer

    HTTP/1.1 used many short or pooled connections that spread across backends; HTTP/2 sends all requests over one long-lived connection, which an L4 balancer pins to one backend.

  3. A client's first visit uses HTTP/2 and later visits use HTTP/3. Why, and how could the first visit use HTTP/3?

    Show answer

    The client learned about h3 from the Alt-Svc header on the first response. Publishing an HTTPS DNS record with alpn=h3 lets it try QUIC before any HTTP exchange.

Sources and further study

Primary

Systems docs

Books

Other curricula

Watch

Free resources

Open in the interactive app: practice, quiz, mock interview →