Engineers ship HTTP-based systems every day. Most operate at the level of "send a request, get a response" — abstracted by client libraries, framework conventions, and observability tooling that hides the protocol underneath. This is enough most of the time. It stops being enough when something at the protocol layer breaks — slow connection establishment, mysterious caching behaviour, head-of-line blocking under load, CORS failures that the error message does not adequately explain. The engineers who understand the protocol deeply debug faster, design more efficient systems, and avoid a recurring class of bugs that surface engineers re-introduce indefinitely.
HTTP/1.1, HTTP/2, HTTP/3 — and Why the Differences Show Up
HTTP/1.1 — text-based, one request per connection (with keep-alive reusing connections sequentially), with workarounds like pipelining that mostly do not work. HTTP/2 — binary framing, multiplexing multiple requests over a single TCP connection, header compression, server push (deprecated in practice). HTTP/3 — built on QUIC over UDP, eliminating TCP head-of-line blocking and reducing connection establishment time. Each generation addressed specific limitations of the previous one. Knowing which version your client and server are negotiating, and the implications of that choice, prevents whole categories of confusion.
Caching: The Headers That Actually Matter
Cache-Control is the centre of HTTP caching, and most teams only use a fraction of its capabilities. max-age, s-maxage, private, public, no-cache, no-store, must-revalidate, stale-while-revalidate, stale-if-error — each has a specific meaning and combining them carelessly produces caching behaviour that nobody expected. ETag and Last-Modified for conditional requests. Vary for cache-key correctness when responses depend on request headers. Mistakes here produce subtle cache pollution, stale content served to wrong users, and CDN bills that grow without explanation.
CORS: The Failure Mode That Catches Everyone
CORS — Cross-Origin Resource Sharing — is famously the source of debugging time disproportionate to its conceptual complexity. The mental model that helps: CORS is enforced by the browser, not the server. The server sets headers describing what is allowed; the browser decides what to actually do based on those headers. Preflight requests (OPTIONS, often invisible to engineers debugging only their main requests) precede certain types of requests. Same-origin policy and CORS interact in ways that are often counterintuitive. Engineers who model CORS as a browser-enforced policy with server-set parameters debug it much faster than engineers who think the server is rejecting the request.
A pattern in load-related incidents: a deployment of new code increases per-request HTTP overhead modestly, the new code is rolled out, and the system begins to degrade at higher request volumes. The root cause is often connection management — exhausted file descriptors on the load balancer, TCP connection pool starvation, HTTP/2 stream limits hit. The protocol-layer causes are invisible to engineers thinking only about their application logic. They become obvious to engineers comfortable with the layer underneath.
Status Codes Beyond 200 and 500
301, 302, 303, 307, 308 — five different redirects with different semantics around method preservation and caching. 401 vs 403 — auth required vs forbidden, often used incorrectly. 422 — unprocessable entity, useful for validation failures. 429 — rate limited, with Retry-After header conventions. 503 vs 504 — service unavailable vs gateway timeout, signalling different upstream issues. Engineers who use the right status codes consistently produce more debuggable systems. Engineers who default to 200 or 500 produce systems that lose information at every error path.
Practical Topics Worth Internalising
- Connection lifecycle in HTTP/2 — multiplexing, stream priorities, head-of-line blocking
- TLS handshake costs and how QUIC reduces them — relevant for mobile and high-latency clients
- Idempotency and safe methods — which methods can be retried, which cannot
- Range requests for large content — partial content, resumable downloads
- Compression negotiation — Accept-Encoding, Content-Encoding, the costs of not compressing
- Authentication mechanisms — Basic, Bearer, Digest, mTLS, signed URLs, with cost/benefit per use case
- Connection management at scale — keep-alive, pooling, timeouts at every layer
When Surface Knowledge Stops Being Enough
For most engineers, most of the time, surface HTTP knowledge is enough. The deeper investment pays back when something at the protocol layer is the bottleneck — performance issues that profilers cannot fully explain, caching behaviour that does not match expectations, scaling issues that show up at the load balancer rather than in the application. The senior engineers who reach into the protocol layer competently in those moments are the engineers whose systems hold up under conditions where less prepared teams produce outages.