HTTP Fundamentals
HTTP is the request/response protocol every web client and server speaks. In its original form it is human-readable text: a client writes a few lines to a socket, the server writes a few lines back, and that is the entire protocol.
The shape of an exchange
Section titled “The shape of an exchange”Open a raw connection and type an HTTP request by hand. This works because HTTP/1.1 is line-based text.
printf 'GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n' \ | openssl s_client -quiet -connect example.com:443 -servername example.comTwo messages go over the wire. Both have the same three-part structure: a start line, zero or more headers, a blank line, then an optional body.
<start line>Header-Name: valueAnother-Header: value
<optional body>The blank line is mandatory — it is how the receiver knows the headers ended. Line endings are CRLF (\r\n).
Request anatomy
Section titled “Request anatomy”POST /api/users?notify=true HTTP/1.1Host: api.example.comContent-Type: application/jsonContent-Length: 41Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...Accept: application/json
{"name":"Ada Lovelace","role":"engineer"}The request line
Section titled “The request line”METHOD SP request-target SP HTTP-version
- Method — the verb.
POSThere. Case-sensitive, always uppercase. - Request target — usually the path plus query string (
/api/users?notify=true). Note it does not include the scheme or host; those live in theHostheader. The exception is requests to a proxy, which use the absolute formhttp://example.com/path. - Version —
HTTP/1.1.
Request headers
Section titled “Request headers”Case-insensitive names, one per line. The ones you will meet constantly:
| Header | Purpose |
|---|---|
Host |
The hostname requested. Required in HTTP/1.1. It is what lets one IP serve many sites (virtual hosting). |
Content-Type |
The media type of the request body, e.g. application/json, application/x-www-form-urlencoded, multipart/form-data. |
Content-Length |
Body size in bytes. Tells the server where the body ends. |
Accept |
Media types the client will accept in the response, with optional quality weights: Accept: application/json, text/html;q=0.8. |
Accept-Encoding |
Compression the client supports: gzip, br, zstd. |
Accept-Language |
Preferred languages: en-GB, en;q=0.9. |
Authorization |
Credentials. Basic <base64> or Bearer <token>. |
User-Agent |
A free-text string identifying the client. Historically messy; treat it as a hint, never as a security control. |
Referer |
The URL of the page that produced this request. (Misspelled in the original spec and never fixed.) |
Cookie |
Cookies previously set by this origin. See cookies and sessions. |
Origin |
The scheme+host+port that initiated a cross-origin request. Sent by the browser, not settable by page JS. |
If-None-Match / If-Modified-Since |
Conditional request validators. See caching. |
The request body
Section titled “The request body”Only some methods carry one. GET bodies are technically allowed by the spec but have no defined semantics and are dropped by many intermediaries — never rely on them.
curl -v https://httpbin.org/post \ -H 'Content-Type: application/json' \ -d '{"name":"Ada"}'Response anatomy
Section titled “Response anatomy”HTTP/1.1 201 CreatedDate: Sun, 09 Aug 2026 12:04:11 GMTContent-Type: application/json; charset=utf-8Content-Length: 58Location: /api/users/42Cache-Control: no-store
{"id":42,"name":"Ada Lovelace","role":"engineer"}The status line
Section titled “The status line”HTTP-version SP status-code SP reason-phrase
The reason phrase (Created) is purely decorative — clients must act on the numeric code. HTTP/2 and HTTP/3 drop it entirely.
Response headers
Section titled “Response headers”| Header | Purpose |
|---|---|
Content-Type |
Media type of the body. Drives how the browser interprets it. |
Content-Length |
Body length in bytes. Omitted when using chunked transfer encoding. |
Content-Encoding |
Compression actually applied: gzip, br. |
Location |
Target URL for a 3xx redirect, or the URL of a newly created resource with 201. |
Set-Cookie |
Instructs the client to store a cookie. May appear multiple times. |
Cache-Control, ETag, Last-Modified, Expires, Vary |
Caching directives and validators. |
Server |
Server software. Informational; often removed. |
WWW-Authenticate |
Sent with 401 to say which auth scheme to use: Bearer realm="api". |
Allow |
Methods a resource supports. Sent with 405. |
Retry-After |
Seconds (or an HTTP date) to wait. Sent with 429 and 503. |
Methods
Section titled “Methods”The method states the client’s intent. Two properties classify them.
- Safe — the request is read-only; it must not change server state. Crawlers and prefetchers assume this.
- Idempotent — sending it N times has the same effect as sending it once. This is what makes automatic retries possible.
| Method | Safe | Idempotent | Body | Typical use |
|---|---|---|---|---|
GET |
Yes | Yes | No | Retrieve a resource. |
HEAD |
Yes | Yes | No | Like GET but response has no body. Check existence, size, or freshness. |
OPTIONS |
Yes | Yes | No | Ask what the server supports. Used by CORS preflight. |
POST |
No | No | Yes | Create a subordinate resource, or any non-idempotent action. |
PUT |
No | Yes | Yes | Replace the resource at this URL with the body. |
PATCH |
No | No | Yes | Apply a partial modification. |
DELETE |
No | Yes | No | Remove the resource. |
The reason POST is not idempotent: POST /orders twice creates two orders. PUT /users/42 twice leaves one user in one state. DELETE /users/42 twice leaves the user deleted both times — the second call returning 404 does not break idempotency, which is about server state, not the response.
PATCH is not idempotent in general because a patch body can be relative ({"op":"increment","field":"views"}). A JSON Merge Patch that sets absolute values happens to be idempotent, but the method makes no such promise.
# Cheap existence + size check, no body transferredcurl -I https://example.com/large-file.zip
# Ask a server what it allowscurl -X OPTIONS -i https://api.example.com/usersStatus codes
Section titled “Status codes”Three digits. The first digit is the class; act on that when you do not recognise the specific code.
1xx — informational
Section titled “1xx — informational”| Code | Meaning |
|---|---|
100 Continue |
The server accepts the headers; send the body. Triggered by a client sending Expect: 100-continue before a large upload. |
101 Switching Protocols |
Response to an Upgrade request. This is how a WebSocket handshake completes. |
2xx — success
Section titled “2xx — success”| Code | Meaning |
|---|---|
200 OK |
Success, body contains the result. |
201 Created |
A resource was created. Should include Location. |
202 Accepted |
Accepted for processing but not done yet. Async jobs. |
204 No Content |
Success, and there is deliberately no body. Common for DELETE and PUT. |
206 Partial Content |
Response to a Range request. Video seeking, resumable downloads. |
3xx — redirection
Section titled “3xx — redirection”| Code | Meaning |
|---|---|
301 Moved Permanently |
Permanent. Browsers and search engines cache it aggressively — hard to undo. |
302 Found |
Temporary. Historically clients rewrote the method to GET, which is why 307/308 exist. |
303 See Other |
Fetch the Location with GET. The correct redirect after a form POST. |
304 Not Modified |
Your cached copy is still valid. No body. |
307 Temporary Redirect |
Like 302 but the method and body must be preserved. |
308 Permanent Redirect |
Like 301 but method-preserving. |
4xx — client error
Section titled “4xx — client error”| Code | Meaning |
|---|---|
400 Bad Request |
Malformed syntax or failed validation. |
401 Unauthorized |
Not authenticated. Misnamed. Must include WWW-Authenticate. |
403 Forbidden |
Authenticated (or not), but not allowed. Re-authenticating will not help. |
404 Not Found |
No resource at this URL. Also used to hide the existence of resources from unauthorised users. |
405 Method Not Allowed |
URL exists, method does not apply. Must include Allow. |
409 Conflict |
State conflict, e.g. a duplicate unique key or an edit collision. |
410 Gone |
Deliberately removed and will not come back. |
413 Content Too Large |
Body exceeds the server’s limit. |
415 Unsupported Media Type |
Wrong Content-Type on the request. |
422 Unprocessable Content |
Syntactically valid but semantically wrong. Widely used for validation failures. |
429 Too Many Requests |
Rate limited. Should include Retry-After. |
5xx — server error
Section titled “5xx — server error”| Code | Meaning |
|---|---|
500 Internal Server Error |
Unhandled exception. The catch-all. |
501 Not Implemented |
The server does not support the method at all. |
502 Bad Gateway |
A proxy got an invalid response from upstream. |
503 Service Unavailable |
Temporarily down or overloaded. |
504 Gateway Timeout |
A proxy’s upstream did not answer in time. |
Statelessness
Section titled “Statelessness”HTTP has no built-in concept of a session. Each request is processed as if the server has never seen the client before. The server may keep state internally, but the protocol carries none.
The practical consequence: anything the server must know about you has to travel in every single request. That is what Cookie headers, Authorization headers, and session IDs in URLs all do — they re-establish context on each round trip.
# Request 1 — the server sets a cookiecurl -i -c jar.txt https://httpbin.org/cookies/set/session/abc123
# Request 2 — the cookie is re-sent, restoring contextcurl -b jar.txt https://httpbin.org/cookies# => {"cookies": {"session": "abc123"}}-c writes a cookie jar, -b reads one. Without the jar, request 2 has no memory of request 1. See cookies, sessions and auth.
Protocol versions
Section titled “Protocol versions”The semantics above — methods, status codes, headers — are identical across all three versions. What changes is how bytes are framed and connections are managed.
HTTP/1.0
Section titled “HTTP/1.0”One request per TCP connection. Connect, request, respond, close. Catastrophically slow: every request pays a full handshake.
HTTP/1.1 — persistent connections
Section titled “HTTP/1.1 — persistent connections”Connections stay open by default (Connection: keep-alive is implicit; Connection: close opts out). Multiple requests reuse one connection sequentially.
The remaining problem is head-of-line blocking: responses must come back in request order on a connection. One slow response stalls everything behind it. Browsers work around this by opening ~6 parallel connections per origin, which is why the old advice was to shard assets across subdomains.
HTTP/1.1 also added Host (enabling virtual hosting), chunked transfer encoding, byte ranges, and the caching model.
curl --http1.1 -v https://example.com/ -o /dev/null> GET / HTTP/1.1> Host: example.com< HTTP/1.1 200 OKHTTP/2 — binary framing and multiplexing
Section titled “HTTP/2 — binary framing and multiplexing”Standardised in 2015. Same semantics, completely different wire format.
- Binary framing. Messages are split into frames (HEADERS, DATA, …) instead of text lines.
- Multiplexing. Many concurrent streams share one TCP connection, interleaved frame by frame. No application-level head-of-line blocking, and no need for 6 connections or domain sharding.
- Header compression (HPACK). Headers repeat almost identically across requests; HPACK keeps a shared dynamic table so repeats cost a few bytes.
- Header names are lowercase on the wire — that is why curl prints
> user-agent:under HTTP/2. - Server push was specified but is now removed from Chrome and effectively dead; use
103 Early Hintsorpreloadinstead.
The catch: HTTP/2 still runs over TCP, so a single lost packet stalls every stream on that connection until it is retransmitted. TCP-level head-of-line blocking replaced the HTTP-level kind.
curl --http2 -v https://example.com/ -o /dev/null# > GET / HTTP/2# < HTTP/2 200In practice browsers only use HTTP/2 over TLS, and the version is chosen during the TLS handshake via ALPN.
HTTP/3 — QUIC
Section titled “HTTP/3 — QUIC”Standardised in 2022 (RFC 9114). HTTP/3 runs over QUIC, a transport built on UDP that reimplements reliability, ordering and congestion control in userspace, with TLS 1.3 built in rather than layered on.
- Independent streams. Loss on one stream does not block others — this fixes TCP head-of-line blocking properly.
- Faster setup. Transport and crypto handshakes are combined: one round trip for a new connection, zero for a resumed one (0-RTT).
- Connection migration. A connection is identified by a connection ID, not the IP/port tuple, so switching from Wi-Fi to cellular does not break it.
- QPACK replaces HPACK, adapted for out-of-order stream delivery.
Discovery is via the Alt-Svc response header (or an HTTPS DNS record): the server answers over HTTP/2 and advertises that HTTP/3 is available, and the client upgrades on a later connection.
curl --http3 -v https://cloudflare.com/ -o /dev/nullComparison
Section titled “Comparison”| HTTP/1.1 | HTTP/2 | HTTP/3 | |
|---|---|---|---|
| Transport | TCP | TCP | QUIC (UDP) |
| Framing | Text | Binary | Binary |
| Concurrency | Sequential, ~6 connections | Multiplexed streams | Multiplexed streams |
| Head-of-line blocking | Application + TCP | TCP only | None |
| Header compression | None | HPACK | QPACK |
| Encryption | Optional | Optional in spec, required in browsers | Always (TLS 1.3) |
Reading real traffic
Section titled “Reading real traffic”# Full picture: DNS, TLS, negotiated protocol, request, responsecurl -v https://example.com/ -o /dev/null
# Just the response headerscurl -sI https://example.com/
# Follow the whole redirect chain and show every hopcurl -sIL https://example.com/ | grep -E '^(HTTP|location)'
# Time each phase of the requestcurl -s -o /dev/null -w 'dns:%{time_namelookup} tcp:%{time_connect} tls:%{time_appconnect} ttfb:%{time_starttransfer} total:%{time_total}\n' \ https://example.com/The last one is the fastest way to tell whether a slow request is DNS, connection setup, TLS, or the server actually thinking.
Key points
Section titled “Key points”- Every HTTP message is a start line, headers, a blank line, and an optional body.
- The method declares intent; safe means read-only, idempotent means retry-safe.
- Status classes matter more than individual codes: 2xx worked, 3xx go elsewhere, 4xx you erred, 5xx the server erred.
Hostis what makes virtual hosting possible and is mandatory in HTTP/1.1.- HTTP is stateless — context must be re-sent on every request.
- HTTP/1.1, /2 and /3 share identical semantics; they differ only in framing, multiplexing and transport.