Guide
HTTP fundamentals explained
When you load a page, submit a form, or call an API, the browser and server exchange HTTP messages — structured text (or binary-framed in HTTP/2+) that says what you want, what you are sending, and what came back. HTTP is stateless: each request carries everything the server needs; the protocol itself does not remember prior requests. Sessions, carts, and login state are built on top with cookies, tokens, and server-side stores. Understanding HTTP is prerequisite to designing REST APIs, tuning caches, debugging CORS errors, and reading nginx access logs. This guide covers request and response anatomy, methods and idempotency, status code families, headers that matter in production, content negotiation, cookies, connection reuse, a Harbor order API traced example, method decision tables, common pitfalls, and a practitioner checklist.
The request-response model
HTTP runs on top of TCP (or QUIC in HTTP/3). A client opens a connection, sends a request, and waits for a response. The server does not push data unless the client asked (WebSockets and server-sent events upgrade this model, but they still begin with an HTTP request).
Request line
The first line has three parts:
METHOD SP REQUEST-TARGET SP HTTP-VERSION. Example:
GET /api/orders/42 HTTP/1.1
GET is the method. /api/orders/42 is the request target (path + optional query string). HTTP/1.1 is the version. In HTTP/2 and HTTP/3 the method and path are framed differently, but the semantics are identical.
Headers and body
After the request line come header lines (Name: value), terminated by
a blank line, then an optional message body (form data, JSON,
file upload). Headers carry metadata: Host (required in HTTP/1.1),
Content-Type, Authorization, Accept,
Cookie, and dozens more.
Response structure
The server replies with a status line
(HTTP/1.1 200 OK), response headers, blank line, and optional body.
The status code tells the client how to interpret the result; headers describe
the body (Content-Type, Content-Length) and caching
policy (Cache-Control, ETag).
HTTP methods and idempotency
Methods express intent. REST APIs map them to resource operations, but HTTP itself only defines semantics — servers may implement differently unless you document a contract.
| Method | Safe | Idempotent | Typical use |
|---|---|---|---|
GET |
Yes | Yes | Read a resource; no body side effects |
HEAD |
Yes | Yes | Like GET but response has no body (check headers/existence) |
POST |
No | No | Create resource, submit form, RPC-style actions |
PUT |
No | Yes | Replace entire resource at URL |
PATCH |
No | No* | Partial update (field-level merge) |
DELETE |
No | Yes | Remove resource |
OPTIONS |
Yes | Yes | Discover allowed methods (CORS preflight) |
Safe methods should not change server state (browsers may
prefetch GET). Idempotent methods produce the same server state
if repeated — critical for retries after network timeouts. A duplicate
POST may create two orders; a duplicate PUT or
DELETE should not. Use
idempotency keys
on POST when clients retry payment or order creation.
Status codes — what the numbers mean
The three-digit code is grouped by hundreds digit. Clients and intermediaries (proxies, CDNs) branch on these families before reading the body.
2xx Success
- 200 OK — standard success with a body.
- 201 Created — resource created; often includes
Locationheader pointing to the new URL. - 204 No Content — success with empty body (common for DELETE).
3xx Redirection
- 301 Moved Permanently — URL changed; update bookmarks and SEO canonicals.
- 302 / 307 / 308 — temporary or permanent redirects; 307/308 preserve the original method (POST stays POST).
- 304 Not Modified — cached copy is still valid (conditional
GET with
If-None-MatchorIf-Modified-Since).
4xx Client error
- 400 Bad Request — malformed syntax or validation failure.
- 401 Unauthorized — authentication required or failed (not "you lack permission" — that is 403).
- 403 Forbidden — authenticated but not allowed.
- 404 Not Found — no resource at this URL (or hidden on purpose).
- 409 Conflict — state conflict (duplicate email, stale version).
- 429 Too Many Requests — rate limited; respect
Retry-After.
5xx Server error
- 500 Internal Server Error — unhandled exception; fix server code.
- 502 Bad Gateway — proxy received invalid response from upstream.
- 503 Service Unavailable — overloaded or in maintenance; often retryable.
- 504 Gateway Timeout — upstream did not respond in time.
APIs should return consistent JSON error bodies with a machine-readable code and human message — see our REST API design guide for error shape conventions.
Essential headers in production
Hundreds of headers exist; these appear in nearly every production debugging session.
- Host — virtual host routing on shared IPs; required in HTTP/1.1.
- Content-Type — MIME type of the body
(
application/json,text/html; charset=utf-8). - Accept — formats the client prefers; drives content negotiation.
- Authorization — credentials (
Bearer <token>, Basic auth). Pair with JWT or OAuth patterns. - Cookie / Set-Cookie — session identifiers; set
HttpOnly,Secure,SameSiteon auth cookies. - Cache-Control — who may cache and for how long; see HTTP caching.
- ETag / If-None-Match — validators for conditional requests.
- Location — redirect target or URL of created resource.
- User-Agent — client identification (browsers, bots, SDKs).
- X-Forwarded-For / X-Forwarded-Proto — client IP and scheme when behind a reverse proxy.
Custom X- headers are deprecated in favor of registered names, but
still common (X-Request-Id for tracing). Prefer
traceparent from OpenTelemetry for distributed traces.
HTTPS, connections, and versions
Plain HTTP sends headers and bodies in cleartext. Production sites use HTTPS — HTTP over TLS — which encrypts the entire conversation and verifies server identity via certificates.
Keep-Alive (Connection: keep-alive) reuses TCP
connections for multiple requests, avoiding repeated handshakes. HTTP/1.1 defaults
to persistent connections. HTTP/2 multiplexes many requests on one connection;
HTTP/3 uses QUIC. See
HTTP/2 and HTTP/3
for when version upgrades matter.
Content negotiation lets one URL serve multiple representations.
A client sends Accept: application/json and
Accept-Language: en-US; the server picks the best match or returns
406 Not Acceptable. APIs usually fix format via URL (/api/v1/...)
instead of negotiating per request.
Worked example: tracing a Harbor order API call
A mobile app fetches order 42 from Harbor Shop. Here is the HTTP/1.1
exchange (simplified; TLS encryption happens below this layer).
Request:
GET /api/v1/orders/42 HTTP/1.1
Host: api.harbor-shop.example
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Accept: application/json
Accept-Encoding: gzip, br
User-Agent: HarborApp/3.2 (iOS)
nginx terminates TLS, reads Host, matches the
/api/ location, and proxies to an upstream Node service. The app
server validates the JWT, queries Postgres, and builds JSON.
Response:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Cache-Control: private, no-store
ETag: "ord-42-v3"
Content-Encoding: gzip
Content-Length: 284
{"id":42,"status":"shipped","total_cents":4999,...}
private, no-store prevents CDNs from caching personalized order
data. ETag lets the app send
If-None-Match: "ord-42-v3" on refresh for a cheap 304 if nothing
changed. If the token expired, the server returns 401 with
WWW-Authenticate — the app refreshes the token and retries the same
idempotent GET.
Method and status decision table
| Goal | Method | Success code | Notes |
|---|---|---|---|
| Fetch one resource | GET |
200 | Add ETag for conditional GET |
| Create with server-assigned ID | POST |
201 + Location | Use idempotency key for retries |
| Full replace by ID | PUT |
200 or 204 | Client sends entire representation |
| Update one field | PATCH |
200 | Document merge semantics (JSON Merge Patch vs JSON Patch) |
| Delete resource | DELETE |
204 | Repeated DELETE should return 204 or 404 |
| CORS preflight | OPTIONS |
204 | Return Access-Control-Allow-* headers |
Common mistakes
- Using GET for state-changing actions — crawlers and prefetchers will trigger them; use POST with CSRF protection.
- Returning 200 with an error JSON body — breaks HTTP-aware clients, caches, and monitoring; use 4xx/5xx status codes.
- Ignoring charset on text responses — always specify
charset=utf-8onContent-Typefor JSON and HTML. - Missing Host header behind proxies — causes wrong virtual host routing or redirect loops.
- Treating 401 and 403 interchangeably — clients handle them differently (re-auth vs give up).
- Chunked uploads without Content-Length limits — enables denial-of-service; cap body size at nginx or API gateway.
- Storing secrets in URLs — query strings land in logs and Referer headers; put tokens in headers or POST body.
Production checklist
- Enforce HTTPS with HSTS; redirect plain HTTP to TLS.
- Document API methods, status codes, and error JSON schema in OpenAPI.
- Set sensible
Cache-Controlper resource type (static vs API). - Log method, path, status, duration, and request ID — not Authorization values.
- Configure max body size and timeouts at the edge (nginx) and app.
- Return
Retry-Afteron 429 and 503 when applicable. - Test CORS preflight (OPTIONS) for browser clients.
- Verify HTTP version and compression with
curl -vor browser DevTools Network tab.
Key takeaways
- HTTP is a stateless request-response protocol — each message stands alone; sessions are layered on with cookies and tokens.
- Methods express intent — GET reads safely; POST creates; PUT/PATCH/DELETE update with different idempotency guarantees.
- Status codes are the contract — use the right family so clients, proxies, and monitors behave correctly.
- Headers carry metadata — Content-Type, Cache-Control, and Authorization decisions shape security and performance.
- HTTPS and connection reuse sit below HTTP semantics but dominate latency in production.
Related reading
- REST API design explained — resource URLs, pagination, versioning, and error shapes on top of HTTP
- TLS and HTTPS explained — certificates, handshakes, and encrypting HTTP traffic
- HTTP caching explained — Cache-Control, ETags, and CDN behavior
- HTTP/2 and HTTP/3 explained — multiplexing, QUIC, and transport upgrades