All articles
HTTPNetworkingSystem Design14 min read

Everything You Need to Know About an HTTP Request

What actually happens between typing a URL and seeing a response: DNS, TCP, TLS, the request line, headers, body, status codes, connection reuse, caching and HTTP/1.1 vs HTTP/2 vs HTTP/3.

Why HTTP exists

Two machines that have never met need to agree on how to ask for something and how to answer. HTTP is that agreement: a text-based, request/response, stateless application protocol. Every message is self-describing — the method says the intent, the path says the resource, the headers say the metadata, the body carries the payload.

The design choices that made it win are worth naming. It is stateless, so any server in a fleet can answer any request and you can scale horizontally by adding boxes. It is uniform, so caches, proxies and load balancers can act on a message without understanding your application. It is text-based (until HTTP/2), so it was trivially debuggable in an era with no tooling.

The full journey of one request

1DNS lookup2TCP connect3TLS handshake4Send request5Server work6Response7Render / parse
Typing a URL to rendering a response: eight distinct systems, most of them cached.
  1. URL parsing. The client splits scheme, host, port, path, query and fragment. The fragment never leaves the browser.
  2. DNS resolution. api.example.com becomes an IP, usually from the OS or browser cache rather than an actual network round trip.
  3. TCP handshake. SYN, SYN-ACK, ACK — one round trip before a byte of your data moves. QUIC (HTTP/3) folds this into the crypto handshake.
  4. TLS handshake. Certificate validation, key exchange, ALPN negotiation (which is how the client and server agree on HTTP/1.1 vs HTTP/2).
  5. Request sent. Request line, headers, blank line, optional body.
  6. Server-side work. Reverse proxy → app server → auth → handler → database → serialisation. This is usually where your latency lives.
  7. Response returned. Status line, headers, body — possibly streamed in chunks.
  8. Connection reuse. The socket stays open for the next request instead of paying handshakes again.
Where the milliseconds actually go
On a cold connection you pay roughly 1 RTT for DNS, 1 for TCP, 1–2 for TLS before the server sees anything. On a warm connection you pay zero. This is why connection pooling in your backend HTTP client is often a bigger win than optimising the handler.

Anatomy of a request

Request linePOST /v1/orders?dryRun=false HTTP/1.1HeadersHost: api.example.comContent-Type: application/jsonAuthorization: Bearer eyJhbGci...Idempotency-Key: 7c9e-41abBody (optional){ "sku": "AB-19", "qty": 2 }
Every HTTP message is a start line, headers, a blank line, then an optional body.
POST /v1/orders?dryRun=false HTTP/1.1
Host: api.example.com
Content-Type: application/json
Content-Length: 27
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...
Idempotency-Key: 7c9e-41ab-9f01

{"sku":"AB-19","qty":2}
A raw HTTP/1.1 request — exactly the bytes on the wire.
  • Method — the intent (GET, POST, PUT, PATCH, DELETE…). Covered in depth in the methods article.
  • Path — identifies the resource. Hierarchical, server-side routing key.
  • Query string — non-hierarchical modifiers: filtering, sorting, pagination.
  • Version — HTTP/1.1 here; HTTP/2 and /3 send the same semantics in binary frames.

The headers that actually matter

HeaderDirectionWhat it decides
HostRequestWhich virtual host / site on a shared IP answers
Content-TypeBothHow to parse the body — JSON, form, multipart
AcceptRequestContent negotiation: what the client can handle
AuthorizationRequestBearer token, Basic, or a signed scheme
Cache-ControlBothWhether and how long anything may cache this
ETag / If-None-MatchBothConditional GET → cheap 304 responses
Content-EncodingResponsegzip / br compression of the body
Set-Cookie / CookieBothSession continuity across stateless requests
X-Request-IdBothCorrelating one user action across many services

Header names are case-insensitive. Order is not significant. Duplicated headers are legal and combined with commas — which is a real source of bugs when a proxy appends to X-Forwarded-For and your code reads only the first value.

The response and status codes

HTTP/1.1 201 Created
Content-Type: application/json
Location: /v1/orders/8812
Cache-Control: no-store

{"id":8812,"status":"pending"}
The mirror image: status line, headers, body.
ClassMeaningOnes you will actually use
1xxInformational101 Switching Protocols (WebSocket upgrade)
2xxSuccess200 OK, 201 Created, 202 Accepted, 204 No Content
3xxRedirection301 permanent, 302/307 temporary, 304 Not Modified
4xxClient error400, 401 unauthenticated, 403 unauthorised, 404, 409 conflict, 422, 429 rate limited
5xxServer error500, 502 bad gateway, 503 unavailable, 504 timeout
401 vs 403, the interview favourite
401 means “I do not know who you are” — send credentials. 403 means “I know exactly who you are and you still cannot do this.” Retrying a 403 with the same token is pointless; retrying a 401 after refreshing a token is correct.

Statelessness, cookies and tokens

HTTP itself remembers nothing between requests. Every request must carry everything the server needs. Sessions are built on top of that: the server issues an opaque cookie or a signed token, and the client replays it each time.

  • Cookies — automatic, sent by the browser, vulnerable to CSRF unless you set SameSite; use HttpOnly and Secure always.
  • Bearer tokens — explicit, immune to CSRF by default, but must be stored somewhere and are hard to revoke before expiry.

Statelessness is what allows a load balancer to send consecutive requests from one user to different servers. The moment you keep session data in a process's memory you have quietly reintroduced sticky sessions and broken horizontal scaling.

Connections: keep-alive, HTTP/2, HTTP/3

VersionTransportKey propertyMain limitation
HTTP/1.1TCPKeep-alive, one request at a time per connectionHead-of-line blocking; browsers open ~6 connections per host
HTTP/2TCP + TLSBinary framing, multiplexed streams, header compression (HPACK), server pushTCP-level head-of-line blocking on packet loss
HTTP/3QUIC over UDPIndependent streams, 0-RTT resumption, connection migration across networksUDP sometimes blocked; newer tooling

The semantics never changed. A GET is a GET in all three. What changed is framing and transport — which is why sprite sheets and domain sharding, sensible under HTTP/1.1, became actively harmful under HTTP/2.

Caching and conditional requests

GET /v1/products/19 HTTP/1.1
If-None-Match: "a3f19c"

HTTP/1.1 304 Not Modified
ETag: "a3f19c"
Cache-Control: max-age=60, stale-while-revalidate=300
A conditional GET that costs almost nothing when nothing changed.
  • max-age — how long a fresh copy may be served without asking.
  • s-maxage — the same, but only for shared caches like a CDN.
  • no-cache — cache it, but revalidate before every use. Not the same as no-store.
  • no-store — never write it anywhere. Use for anything authenticated and personal.
  • Vary — the header set that partitions the cache key. Forgetting Vary: Authorization is how one user's data reaches another.

How to debug a request properly

curl -v -X POST https://api.example.com/v1/orders \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer $TOKEN' \
  -d '{"sku":"AB-19","qty":2}' \
  -w '\ndns=%{time_namelookup} tls=%{time_appconnect} ttfb=%{time_starttransfer} total=%{time_total}\n'
curl tells you more in one line than a browser devtools tab.

Those timings split the problem instantly. High time_namelookup is DNS. High time_appconnect is TLS or a distant region. High time_starttransferwith a low total is server-side work. A slow total with fast TTFB is a large or slowly streamed body.

Interview framing

“HTTP is a stateless request/response protocol. A cold request pays DNS, TCP and TLS round trips before any application work, so we pool connections and keep HTTP/2 to a single origin. Reads are cacheable with ETags and stale-while-revalidate at the CDN; writes are no-store and carry an idempotency key so retries after a 504 are safe. Auth is a bearer token rather than a server-side session so any instance can serve any request.”

Keep reading

Suggested next articles based on this one.

Design it, don't just read it.

Practise LLD and system design problems with structured rubrics and AI feedback.

Start practising free