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 Location header 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-Match or If-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, SameSite on 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-8 on Content-Type for 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-Control per 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-After on 429 and 503 when applicable.
  • Test CORS preflight (OPTIONS) for browser clients.
  • Verify HTTP version and compression with curl -v or 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