Every time you load a web page, your computer has a structured conversation with another computer somewhere in the world. The conversation has a specific shape. It starts with a request, ends with a response, and in between passes through a precisely defined dance of headers, bodies, and status codes. That conversation is HTTP. It’s one of the most important protocols on the internet, and also one of the most misunderstood, because most developers use it every day without ever reading what it actually says.
Before HTTP: the web didn’t exist
In 1989, Tim Berners-Lee was working at CERN, the European particle physics laboratory, when he wrote a proposal titled “Information Management: A Proposal.” The document described a system for sharing and linking research documents across the laboratory’s network. His manager, Mike Sendall, famously wrote “vague, but exciting” at the top.
By 1991, Berners-Lee had built the first version of what he was now calling the World Wide Web. It had three components: a markup language for writing documents (HTML), a way to address documents across the network (URLs), and a protocol for fetching those documents from one computer to another. That protocol was HTTP, the HyperText Transfer Protocol.
The first version, HTTP/0.9, was astonishingly simple. A client sent a single line: GET /path/to/document. The server sent back the document. That was it. No headers. No status codes. No content types. The only method was GET. There was no way to say “this failed” or “this moved” or “this is HTML rather than plain text.” If the server didn’t have the document, it just closed the connection.
HTTP/0.9 was enough to launch the web. It wasn’t enough to grow it.
HTTP/1.0 and the birth of the request-response model
In 1996, HTTP/1.0 was published as RFC 1945. It introduced most of the concepts we still use today.
An HTTP/1.0 request looked like this:
GET /index.html HTTP/1.0
Host: www.example.com
User-Agent: Mozilla/4.0
Accept: text/html
A request has three parts. The request line at the top says what to do (GET), what to do it to (/index.html), and what version of the protocol to use (HTTP/1.0). Then a series of headers: key-value pairs that carry metadata about the request. Then an empty line. Then, optionally, a body containing data the request is sending to the server (empty for GET requests; populated for POST, PUT, and similar).
A response looks similar:
HTTP/1.0 200 OK
Content-Type: text/html
Content-Length: 1354
Date: Sat, 28 Nov 2026 06:00:00 GMT
<html>
<head>...
The status line at the top says the version (HTTP/1.0), the status code (200), and a human-readable reason phrase (OK). Then headers. Then an empty line. Then the body: the actual document the client asked for.
That shape (request line, headers, blank line, body) is the fundamental unit of HTTP. It hasn’t really changed since 1996. Every HTTP request and response you’ve ever seen, from loading a web page to calling a JSON API, follows this same structure. Understanding it is maybe the single most valuable thing you can learn about the web.
Methods: what you’re asking the server to do
HTTP/1.0 introduced the idea of different methods: verbs that describe what the request is for. HTTP/1.1 extended the list. The common ones are:
- GET: “Please give me this resource.” Should be safe and idempotent (making the same request twice should have the same effect as making it once).
- POST: “Here is some data; do something with it.” Used for submitting forms, creating resources, triggering actions.
- PUT: “Here is a resource; store it at this URL.” Idempotent: repeating the request has the same effect.
- DELETE: “Remove the resource at this URL.” Also idempotent.
- HEAD: “Send me the headers for this resource, but not the body.” Useful for checking if something exists or has changed.
- PATCH: “Apply these partial changes to the resource.”
- OPTIONS: “What can I do with this URL?” Used for CORS preflight checks.
The spec describes these methods as having specific properties: safety (GET should not change server state), idempotency (PUT and DELETE should be repeatable without harm), cacheability. In practice, lots of applications break these conventions, and lots of developers don’t know the conventions exist. But the specification is clear, and tools along the network path (browsers, proxies, CDNs) assume the conventions hold.
Status codes: the server’s reply in three digits
Every response carries a status code. The first digit of the code tells you the category:
- 1xx: Informational. “Still working on it.” Rare in practice.
- 2xx: Success. Your request was understood and processed.
- 3xx: Redirection. “The thing you’re looking for is somewhere else.”
- 4xx: Client error. “You made a mistake.”
- 5xx: Server error. “I made a mistake.”
The most common ones are worth memorising:
- 200 OK: “Here’s what you asked for.”
- 201 Created: “I made a new resource based on your request.”
- 204 No Content: “Your request succeeded, and I have nothing to say about it.”
- 301 Moved Permanently: “This URL has changed. Update your bookmarks. Caches should remember this.”
- 302 Found / 307 Temporary Redirect: “Go here instead, just for now.”
- 304 Not Modified: “Your cached copy is still good.”
- 400 Bad Request: “I can’t parse what you sent me.”
- 401 Unauthorized: “You need to authenticate.”
- 403 Forbidden: “You’re authenticated, but you can’t do that.”
- 404 Not Found: “The resource at this URL doesn’t exist.”
- 429 Too Many Requests: “Slow down.”
- 500 Internal Server Error: “Something broke on my side.”
- 502 Bad Gateway: “I’m a proxy and the server behind me gave me a bad response.”
- 503 Service Unavailable: “I’m overloaded or down for maintenance.”
- 504 Gateway Timeout: “I’m a proxy and the server behind me took too long.”
A good developer learns to read status codes the way a doctor reads vital signs. 502 from a load balancer means “your backend is broken.” 504 means “your backend is slow.” 429 means “somebody is misbehaving.” Each one tells a specific story about where in the chain things went wrong.
Headers: where all the interesting stuff lives
HTTP headers are where most of the complexity of the modern web lives. They carry everything that isn’t the body: authentication tokens, cache directives, content negotiation, cookies, compression, caching instructions, security policies, rate limits, and hundreds of other things.
Some of the headers you’ll see most often:
- Host: Which hostname the request is for. This is how a single IP address can serve multiple websites; the server reads the Host header to decide which site to return.
- User-Agent: What software is making the request. Originally meant for content negotiation, now a tangled mess of historical fake identifiers.
- Accept: What content types the client understands.
Accept: application/jsonmeans “please give me JSON.”Accept: text/html,application/xhtml+xmlmeans “HTML is fine.” - Content-Type: The type of data in the body.
application/json,text/html,image/png,multipart/form-data, and so on. - Content-Length: How many bytes the body contains.
- Authorization: Credentials for authenticated requests.
Authorization: Bearer <token>is the most common pattern today. - Cookie / Set-Cookie: State that persists across requests. The server can tell the client “remember this,” and the client sends it back on subsequent requests.
- Cache-Control: Instructions for how to cache the response.
Cache-Control: max-age=3600means “you can serve this from cache for an hour.” - ETag / If-None-Match: A kind of fingerprint for a resource. The client stores the ETag it received; next time it asks for the resource, it sends
If-None-Match: <etag>. If the resource hasn’t changed, the server replies with 304 Not Modified and no body, saving bandwidth. - Location: Where to go on a redirect.
301 Moved PermanentlywithLocation: /new-urltells the client to fetch/new-urlinstead.
The genius of headers is that they’re extensible. New use cases get new headers without breaking existing ones. CORS, content security policies, HSTS, rate limit information: all of these were added to HTTP by defining new headers, without changing the protocol itself.
HTTP/1.1: keeping connections alive
HTTP/1.0 had a huge flaw: every request required a new TCP connection. The client connected, sent the request, got the response, and the connection closed. Loading a page with thirty images meant thirty-one separate connections: one for the HTML, one for each image. On slow networks, most of the time was spent in connection setup rather than actual data transfer.
HTTP/1.1, published in 1997, solved this with persistent connections. By default, connections stay open after a request completes. The client can send another request on the same connection without paying the setup cost again.
It also introduced pipelining: the idea that a client could send multiple requests on a single connection without waiting for each response. In practice, pipelining never worked well because servers had to return responses in the same order as requests (head-of-line blocking), and most browsers disabled it.
HTTP/1.1 also made the Host header mandatory, which enabled virtual hosting: multiple websites on a single IP address. Before HTTP/1.1, hosting providers needed a separate IP for each site they served.
HTTP/2: multiplexing and binary framing
By the 2010s, web pages had become enormous: dozens of CSS files, hundreds of JavaScript files, thousands of images. HTTP/1.1’s one-request-at-a-time model was a bottleneck. Browsers worked around it by opening six parallel connections per domain, but that only helped so much.
HTTP/2, published in 2015, was a major rework. It kept the same semantics (the same methods, status codes, and headers) but changed the wire format completely. Instead of text lines, HTTP/2 uses binary frames. Instead of one request at a time, it multiplexes multiple requests onto a single connection, interleaving their frames. This eliminates head-of-line blocking at the HTTP layer.
HTTP/2 also introduced header compression (via HPACK), which matters because web pages send the same headers over and over (User-Agent, Cookie, Accept) and those headers can easily total several kilobytes per request.
HTTP/2 was a big step forward, but it still ran over TCP, which has its own head-of-line blocking problem at the network layer: if a single packet is lost, all streams on the connection stall waiting for retransmission.
HTTP/3: HTTP without TCP
HTTP/3 solves the TCP head-of-line blocking problem by running HTTP over a new transport protocol called QUIC, which runs over UDP instead of TCP. QUIC is a reliable transport in its own right, but it handles multiplexing at the transport layer, so a lost packet on one stream doesn’t stall other streams.
HTTP/3 also folds TLS into the transport, reducing the number of round trips needed to establish a secure connection. On fast networks this barely matters. On slow or high-latency networks (mobile, satellite, long-haul) it can halve the time to first byte.
HTTP/3 adoption is growing but uneven. Major providers (Google, Cloudflare, Meta) support it. Many servers still don’t. It will probably be the dominant HTTP version by 2030, though HTTP/1.1 and HTTP/2 will persist for a long time.
REST, and what it actually is
You can’t write about HTTP without mentioning REST. It’s a term that gets thrown around loosely, often to mean “a web API that returns JSON.” That’s not what it means.
REST, Representational State Transfer, was coined by Roy Fielding in his 2000 PhD dissertation. Fielding was one of the authors of HTTP/1.1, and REST was his attempt to describe the architectural style that HTTP was designed around. REST has specific constraints: stateless communication, uniform interface, resource identification through URIs, manipulation of resources through their representations, hypermedia as the engine of application state.
Most APIs that call themselves REST don’t meet all of these constraints. In particular, almost nobody implements hypermedia (HATEOAS). What most people mean by “REST” in 2026 is “an HTTP API organised around resources, using HTTP methods and status codes idiomatically.” That’s not strictly REST. It’s fine. Don’t worry about it too much, but do learn to use HTTP methods and status codes correctly, because the network between you and your users is full of caches, proxies, and load balancers that assume you’re following the conventions.
Why understanding HTTP matters
HTTP is the substrate of the modern web. Every API call, every page load, every image fetch, every WebSocket upgrade starts as an HTTP request. If you understand the request-response model, the methods, the status codes, and the major headers, you can debug almost any web problem by reading the traffic.
The tools for reading HTTP traffic are freely available. Your browser’s developer tools show you every request and response in detail. curl on the command line lets you construct requests by hand. Tools like mitmproxy and Charles Proxy let you intercept traffic between any client and server. Learning to read HTTP traffic is one of the best investments a web developer can make, because when something goes wrong, the traffic tells you exactly what happened.
HTTP is old now. It’s survived four major revisions and countless extensions. But the core model, a text-based request with headers and a body, met by a status-coded response with headers and a body, has barely changed since 1996. That’s not a sign of staleness; it’s a sign that Berners-Lee and his collaborators got something fundamental right.
The web still runs on what they designed. Thirty years in, it shows no sign of stopping.