HTTP is the protocol that moves data between clients and servers on the web. Every API call, page load, and image fetch goes over it. The networks post covered the full request flow top to bottom — this goes deeper on HTTP itself.
METHOD /path HTTP/version
Header-Name: value
Another-Header: value
Body (optional)
A real GET request:
GET /api/users/42 HTTP/2
Host: api.example.com
Authorization: Bearer eyJhbGci...
Accept: application/json
Accept-Encoding: gzip, br
User-Agent: Mozilla/5.0
A POST with a body:
POST /api/users HTTP/2
Host: api.example.com
Content-Type: application/json
Content-Length: 42
{"name": "Alice", "email": "alice@example.com"}
HTTP/version STATUS_CODE Reason Phrase
Header-Name: value
Body
HTTP/2 200 OK
Content-Type: application/json
Content-Length: 87
Cache-Control: max-age=3600
ETag: "abc123"
{"id": 42, "name": "Alice", "email": "alice@example.com"}
| Method | Idempotent | Safe | Typical use |
|---|---|---|---|
GET |
Yes | Yes | Retrieve a resource |
HEAD |
Yes | Yes | Like GET but no body — check headers, test existence |
OPTIONS |
Yes | Yes | What methods does this endpoint support? (also preflight) |
POST |
No | No | Create a resource, submit data |
PUT |
Yes | No | Replace a resource entirely |
PATCH |
No | No | Partial update |
DELETE |
Yes | No | Remove a resource |
Idempotent means calling it multiple times has the same effect as calling it once. PUT replacing a user with the same data is a no-op on the second call. POST creating a user twice creates two users.
| Range | Meaning |
|---|---|
| 1xx | Informational — request received, keep going |
| 2xx | Success |
| 3xx | Redirect |
| 4xx | Client error — you did something wrong |
| 5xx | Server error — the server did something wrong |
The ones you'll actually see:
| Code | Name | When |
|---|---|---|
| 200 | OK | Request succeeded |
| 201 | Created | Resource created (POST/PUT) |
| 204 | No Content | Success, no body (DELETE, PUT) |
| 301 | Moved Permanently | Permanent redirect; browsers and bots update bookmarks |
| 302 | Found | Temporary redirect; don't update bookmarks |
| 304 | Not Modified | Cached version is still valid |
| 307 | Temporary Redirect | Same as 302 but preserves the HTTP method |
| 308 | Permanent Redirect | Same as 301 but preserves the HTTP method |
| 400 | Bad Request | Malformed request, invalid params |
| 401 | Unauthorized | Not authenticated |
| 403 | Forbidden | Authenticated but not allowed |
| 404 | Not Found | Resource doesn't exist |
| 405 | Method Not Allowed | That method isn't supported here |
| 409 | Conflict | State conflict — duplicate create, stale update |
| 410 | Gone | Resource permanently deleted |
| 422 | Unprocessable Entity | Valid format, but failed validation |
| 429 | Too Many Requests | Rate limited |
| 500 | Internal Server Error | Something broke server-side |
| 502 | Bad Gateway | Upstream server returned garbage |
| 503 | Service Unavailable | Overloaded or in maintenance |
| 504 | Gateway Timeout | Upstream didn't respond in time |
301 vs 302: browsers follow both, but only cache 301s. 302 vs 307: both are temporary, but 302 allows browsers to change POST to GET on the redirect; 307 doesn't.
| Header | What it does |
|---|---|
Host |
Target domain — required in HTTP/1.1 |
Authorization |
Credentials (Bearer <token>, Basic <base64>) |
Content-Type |
Media type of the request body |
Accept |
Media types the client can handle |
Accept-Encoding |
Compression formats the client supports (gzip, br, zstd) |
Accept-Language |
Preferred language (en-US, en;q=0.9) |
User-Agent |
Client identifier |
Cookie |
Cookies for the domain |
Referer |
URL of the page that made this request |
Origin |
Origin of the request (for CORS) |
If-None-Match |
Send cached ETag — server returns 304 if still valid |
If-Modified-Since |
Send cached date — server returns 304 if not changed |
| Header | What it does |
|---|---|
Content-Type |
Media type of the response body |
Content-Length |
Body size in bytes |
Content-Encoding |
Compression applied (gzip, br) |
Location |
URL to redirect to (3xx responses) |
Set-Cookie |
Set a cookie |
ETag |
Version identifier for the response |
Last-Modified |
When the resource last changed |
Cache-Control |
Caching directives |
Vary |
Which request headers affect the cached response |
Strict-Transport-Security |
Force HTTPS for a duration |
X-Frame-Options |
Prevent clickjacking (DENY, SAMEORIGIN) |
Content-Security-Policy |
Restrict what resources the page can load |
Good caching makes everything faster. Cache-Control is how you tell caches what to do.
| Directive | Who it applies to | Meaning |
|---|---|---|
max-age=N |
Browser + CDN | Cache for N seconds |
s-maxage=N |
CDN only | CDN cache duration (overrides max-age for CDNs) |
no-cache |
Both | Cache it, but always revalidate before using |
no-store |
Both | Don't cache at all |
private |
Browser only | Don't store in shared caches (CDNs) |
public |
Both | Can be stored in any cache |
must-revalidate |
Both | Expired cache must revalidate before use |
immutable |
Both | Content won't change — skip revalidation |
no-cache doesn't mean don't cache — it means always check if the cached version is still fresh. For truly no caching, use no-store.
ETags are fingerprints. Server sends one with the response; browser stores it. Next request, browser sends If-None-Match: "etag-value". If the resource hasn't changed, server returns 304 Not Modified with no body. Browser uses its cached copy. Saves bandwidth, same end result.
Same idea with Last-Modified / If-Modified-Since using timestamps instead of hashes.
Vary: Accept-Encoding tells caches to store separate versions for each encoding. Without it, a CDN might serve a gzip'd response to a client that requested plain text.
Set by the server with Set-Cookie, sent by the browser on every matching request.
Set-Cookie: session=abc123; Path=/; Domain=example.com; Expires=Fri, 01 Jan 2027 00:00:00 GMT; Secure; HttpOnly; SameSite=Strict
| Attribute | What it does |
|---|---|
Secure |
Only sent over HTTPS |
HttpOnly |
Not accessible via JavaScript — protects against XSS token theft |
SameSite=Strict |
Only sent on same-site requests — prevents CSRF |
SameSite=Lax |
Sent on top-level navigations but not cross-site sub-requests |
SameSite=None |
Sent on all requests — requires Secure |
Domain |
Which domains receive the cookie |
Path |
Which paths receive the cookie |
Expires / Max-Age |
Persistent cookie; no expiry = session cookie |
HttpOnly + Secure + SameSite=Strict is the baseline for anything sensitive.
Browsers block JavaScript from reading cross-origin responses unless the server explicitly allows it. This is CORS (Cross-Origin Resource Sharing).
Same origin = same scheme + host + port. https://api.example.com and https://example.com are different origins.
For simple requests (GET/POST with basic content types), the browser just adds an Origin header and checks the response:
Response: Access-Control-Allow-Origin: https://yourapp.com
For non-simple requests (DELETE, custom headers, application/json bodies), the browser sends a preflight OPTIONS request first:
OPTIONS /api/data HTTP/2
Origin: https://yourapp.com
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: Authorization
Server responds with what it allows:
Access-Control-Allow-Origin: https://yourapp.com
Access-Control-Allow-Methods: GET, POST, DELETE
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Max-Age: 86400
If the server doesn't respond with the right headers, the browser blocks the request — the network request still happens, the browser just won't give JavaScript the response.
Access-Control-Allow-Credentials: true allows cookies and auth headers on cross-origin requests. Can't be combined with Access-Control-Allow-Origin: *.
Servers compress response bodies; clients say what they support in Accept-Encoding.
| Encoding | Notes |
|---|---|
gzip |
Widely supported, solid compression |
br (Brotli) |
Better compression than gzip, especially for text — use this when you can |
zstd |
Very fast, good compression — increasingly supported |
deflate |
Old, avoid |
Server adds Content-Encoding: gzip to indicate the body is compressed. Client decompresses transparently. Typical compression saves 60–80% on HTML/JSON/CSS.
| Version | Transport | Key feature |
|---|---|---|
| HTTP/1.0 | TCP | New connection per request |
| HTTP/1.1 | TCP | Persistent connections, pipelining (mostly unused), chunked transfer |
| HTTP/2 | TCP + TLS | Binary framing, multiplexing, header compression (HPACK), server push |
| HTTP/3 | QUIC (UDP) | Built-in encryption, eliminates TCP head-of-line blocking |
HTTP/1.1 introduced keep-alive connections. Browsers work around its per-connection limitations by opening 6–8 parallel connections per origin.
HTTP/2 multiplexes multiple requests over one connection — no need for multiple connections or domain sharding. Binary protocol, so no more text parsing. Header compression with HPACK cuts overhead significantly.
HTTP/3 moves to QUIC, which runs over UDP. TLS 1.3 is baked in. Losing a packet in HTTP/2 stalls all streams on the TCP connection — HTTP/3 only stalls the one stream that lost the packet.
Check what version a site is running:
curl -I --http2 https://example.com
curl -I --http3 https://example.com