HTTP Versions Explained: From HTTP/0.9 to HTTP/3

A clear, complete guide to every major HTTP version — what changed, why it mattered, and how that affects real applications today.

Page content

HTTP is how browsers, mobile apps, APIs, and microservices exchange data on the web. Saying “we use HTTP” is incomplete: HTTP/1.1, HTTP/2, and HTTP/3 behave differently under load, and those differences show up as latency, connection counts, and debugging surprises.

This article is a practical tour of every major HTTP version — enough depth to reason about performance and compatibility, without requiring you to read the RFCs cover to cover.

Why HTTP versions matter

Each version kept the familiar request/response model (GET /path, status codes, headers) while changing how bytes move on the wire:

ConcernWhy version choice matters
Latency on high-RTT networksFewer round trips and better connection reuse matter more
Many small assets (JS/CSS/images)Older HTTP suffers from head-of-line blocking and connection limits
Mobile networksPacket loss and connection migration matter (HTTP/3’s strength)
Proxies / corporate middleboxesOlder cleartext TCP assumptions can block newer protocols
ObservabilityDebugging HTTP/2 frames or QUIC is different from reading plain text HTTP/1.1

You still write the same application-level HTTP most of the time. The version mostly changes the transport and framing underneath.

Quick comparison

VersionYear (approx.)TransportFramingMultiplexingNotable ideas
HTTP/0.91991TCPPlain textNoRequest line only; response is raw body
HTTP/1.01996TCPPlain textNoHeaders, status codes, one request per connection
HTTP/1.11997 / refined laterTCPPlain textPipelining (rarely useful)Persistent connections, Host, chunked transfer
HTTP/22015TCP (usually TLS)Binary framesYes (streams)HPACK, single connection per origin
HTTP/32022QUIC over UDPBinary framesYesTLS 1.3 built-in, better loss recovery, connection migration

HTTP/0.9 — the simplest web

HTTP/0.9 was almost a one-liner protocol:

  1. Client opens TCP to the server.
  2. Client sends something like GET /index.html.
  3. Server responds with the document body and closes the connection.
  4. No status codes. No headers. No methods beyond GET in practice.

It proved the idea of a simple retrieval protocol. It did not survive as a production API surface, but it explains why later versions kept the method + path shape.

HTTP/1.0 — headers and status codes arrive

HTTP/1.0 introduced the building blocks developers still use daily:

  • Request line: GET /path HTTP/1.0
  • Response status: HTTP/1.0 200 OK
  • Headers: Content-Type, Content-Length, Last-Modified, and more
  • Methods expanding beyond GET (POST, etc.)

The big limitation

By default, each request typically used its own TCP connection. TCP handshakes (and later TLS handshakes) are expensive relative to fetching a tiny CSS file. Pages with dozens of assets paid that cost repeatedly.

HTTP/1.0 could use Connection: keep-alive in practice, but persistent connections were standardized cleanly in HTTP/1.1.

HTTP/1.1 — the long-running workhorse

HTTP/1.1 became the default language of the web for a long time. Even today, lots of backend services, health checks, curl workflows, and internal APIs speak HTTP/1.1 comfortably.

What HTTP/1.1 added (and why it stuck)

1. Persistent connections (keep-alive)
Connections stay open by default so many requests can reuse one TCP (+ TLS) session.

2. Mandatory Host header
Required for virtual hosting — many sites on one IP.

3. Chunked transfer encoding
Stream a response without knowing Content-Length up front.

4. Better caching headers and content negotiation
Cache-Control, ETag, Accept, Accept-Encoding, and related machinery matured here.

5. Pipelining
Send multiple requests without waiting for each response. In theory helpful; in practice, head-of-line blocking and buggy proxies made pipelining unreliable. Browsers largely avoided it.

Head-of-line blocking in HTTP/1.1

On one connection, responses must come back in order. If request #1 is slow (large or generating slowly), request #2 and #3 wait — even if their responses are ready.

Browsers worked around this with multiple parallel connections per origin (historically up to ~6). That helped, but also:

  • Multiplied TLS/TCP overhead
  • Increased server resource usage
  • Encouraged domain sharding hacks

HTTP/1.1 is still excellent for many APIs. It becomes awkward when a single page needs many concurrent downloads over high latency.

HTTP/2 — binary, multiplexed, one connection

HTTP/2 kept HTTP semantics (methods, status codes, headers) but reinvented the session layer.

Core ideas

Binary framing
Messages are broken into frames (HEADERS, DATA, SETTINGS, WINDOW_UPDATE, …). This is harder to read by eye than HTTP/1.1 text, but far more efficient for machines.

Streams and multiplexing
Many request/response exchanges share one TCP connection as independent streams. A large download should not block a small API call on the same connection the way HTTP/1.1 ordering did (at the HTTP layer).

HPACK header compression
Headers repeat a lot (cookie, user-agent, :path). HPACK compresses them with static/dynamic tables, cutting overhead for chatty APIs and asset-heavy pages.

Stream prioritization (evolved over time)
Browsers can express which resources matter first. Real-world prioritization has been revised (including later “extensible prioritization” work) because early schemes were hard to get right.

Server push (mostly cooled off)
Servers could push resources before the client asked. Cache interactions and wasted bandwidth made push less valuable than hoped; many deployments disable it.

TLS and ALPN

On the public web, HTTP/2 is typically negotiated with TLS using ALPN (h2). Cleartext h2c exists but is uncommon in browsers.

Important caveat: TCP head-of-line blocking remains

HTTP/2 fixes HTTP-level HOL blocking with streams. It does not remove TCP-level HOL blocking: a lost packet can stall the entire connection’s stream set until TCP recovers. That pain is a major motivation for HTTP/3.

Practical impact for developers

  • Prefer HTTP/2 for public websites and high-fan-out clients.
  • You no longer need exotic domain sharding for performance theater.
  • Debugging often moves to browser DevTools Protocol panels, Wireshark, or server frame logs — not raw telnet.
  • GZIP/Brotli still matter; HTTP/2 is not a replacement for body compression.

HTTP/3 — HTTP over QUIC

HTTP/3 maps HTTP semantics onto QUIC, a transport built on UDP.

Why QUIC?

TCP provides reliable ordered delivery for one bytestream. That is great for a single file, less great when many independent streams share one connection and a loss occurs.

QUIC:

  • Runs mostly in user space (faster evolution than kernel TCP)
  • Integrates TLS 1.3 into the handshake (fewer round trips to secure start)
  • Gives each stream independent loss recovery (much better under packet loss)
  • Supports connection migration (e.g., Wi‑Fi → cellular) via connection IDs instead of the TCP 4-tuple alone
  • Enables 0-RTT resumption in some cases (with replay considerations)

What stays familiar

From an application developer’s seat:

  • You still have requests, responses, headers, status codes
  • You still terminate TLS at a load balancer / CDN / reverse proxy most of the time
  • Many frameworks do not change your handler code when the edge speaks HTTP/3

What changes operationally

  • UDP must be allowed on path (some corporate networks still struggle)
  • Middleboxes that “understand TCP HTTP” may not understand QUIC
  • CPU profiles and observability tools differ (QUIC stacks, qlog, etc.)
  • Fallbacks to HTTP/2 / HTTP/1.1 remain important for reachability

When HTTP/3 shines

  • Mobile users, roaming networks, lossy links
  • Connection reuse across network changes
  • Latency-sensitive pages where handshake costs matter
  • Workloads previously hurt by TCP HOL blocking under loss

Side-by-side: transferring many resources

Imagine a page needing 30 small assets:

HTTP/1.1
Open several connections, fight the per-origin connection limit, risk queueing, pay repeated handshake costs if keep-alive is imperfect.

HTTP/2
Usually one connection, many streams, compressed headers — excellent on clean networks. A single congested TCP path can still stall everything.

HTTP/3
Many streams over QUIC; a lost packet is less likely to freeze unrelated streams. Handshake can be cheaper on repeat visits.

TLS versions and HTTP versions

They are related but not identical:

  • HTTPS encrypts HTTP traffic
  • Browsers effectively require TLS for HTTP/2 and HTTP/3 on the open web
  • HTTP/3 assumes TLS 1.3-style cryptography as part of QUIC
  • You can still serve HTTP/1.1 over TLS 1.2 in many setups (though modern configs prefer TLS 1.2+ with strong ciphers, and TLS 1.3 where possible)

Upgrade paths are often: enable modern TLS → enable HTTP/2 at the edge → enable HTTP/3 where UDP works.

What you should use today

Public websites and user-facing APIs behind a CDN

  • Terminate HTTP/2 widely
  • Enable HTTP/3 at the CDN/load balancer when your audience benefits and UDP paths are healthy
  • Keep HTTP/1.1 fallback for older clients and odd networks

Internal microservices

  • HTTP/1.1 or HTTP/2 both appear in production
  • gRPC commonly relies on HTTP/2 features (streams, trailers)
  • Prefer what your mesh/proxy supports cleanly; consistency beats chasing versions

Simple scripts and health checks

  • HTTP/1.1 remains totally fine
  • Reach for HTTP/2/3 when connection churn or concurrency becomes measurable

How version negotiation usually works

  1. Client connects with TLS and offers ALPN protocols (h2, http/1.1, …).
  2. Server selects a mutually supported protocol.
  3. For HTTP/3, clients often discover endpoints via Alt-Svc (or similar HTTPS DNS records in some ecosystems) after an initial connection, then switch for later requests.

You typically configure this on nginx, Envoy, Caddy, cloud load balancers, or CDN settings rather than in application business logic.

Debugging cheat sheet

SymptomThings to check
Site “stuck” on HTTP/1.1TLS config, ALPN, CDN feature flags
HTTP/2 enabled but no speedupHuge single payloads, server think-time, no caching, blocking first-party API calls
HTTP/3 never appearsUDP blocked, Alt-Svc missing, intermediary stripping advertisements
Intermittent mobile failuresFlaky UDP paths, compare HTTP/2 fallback quality
Weird proxy errors after upgradeProtocol-aware middleboxes; try HTTP/2 only

Browser DevTools (Network → Protocol column), curl --http2 / --http3 (where supported), and CDN analytics are the fastest first tools.

Mental model to keep

  1. Semantics stayed stable: methods, status codes, headers, URLs.
  2. Transport & framing changed: text → binary frames → QUIC streams.
  3. HTTP/1.1 optimized connection reuse.
  4. HTTP/2 optimized many requests on one TCP connection.
  5. HTTP/3 optimized that story further for lossy and mobile networks by leaving TCP.

If you remember only that progression — reuse → multiplex on TCP → multiplex on QUIC — you already understand why the versions exist.

Summary

  • HTTP/0.9 / 1.0: historical foundations; taught us requests, then headers/status.
  • HTTP/1.1: still the reliable baseline; persistent connections and the modern header model.
  • HTTP/2: binary multiplexing + HPACK; default performance protocol for many sites.
  • HTTP/3: HTTP over QUIC/UDP; best under loss and roaming; growing at the edge.

For most teams: serve HTTP/2 everywhere you can, add HTTP/3 at the edge when available, keep HTTP/1.1 as fallback, and measure real user latency instead of upgrading for fashion.