HTTP/3 is faster than HTTP/2, but not because it’s fundamentally a better protocol at the application layer; it’s because it uses UDP instead of TCP.

Let’s see what that looks like in practice. We’ll use nghttp2 for HTTP/2 and nghttp3 for HTTP/3, both with nghttpd as the server. We’ll simulate some network conditions using tc to add latency and packet loss.

First, set up the server. nghttpd can serve both protocols if you have the right flags.

# For HTTP/2
nghttpd -p 8080 -d /path/to/your/document/root/ http2_server 8080

# For HTTP/3 (requires QUIC support, typically built with BoringSSL or OpenSSL 3.0+)
# You'll need a certificate and key. For testing, self-signed is fine.
# openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes -subj "/CN=localhost"
nghttpd -p 8443 --quic-private-key=key.pem --quic-public-cert=cert.pem -d /path/to/your/document/root/ http3_server 8443

Now, a client. We’ll use nghttp to fetch a small file (e.g., index.html) and a larger one (e.g., large_file.zip).

# HTTP/2 client
nghttp -v -H "Host: localhost" http://localhost:8080/index.html
nghttp -v -H "Host: localhost" http://localhost:8080/large_file.zip

# HTTP/3 client
nghttp -v -H "Host: localhost" --no-tls-check --quic-version=h3-29 https://localhost:8443/index.html
nghttp -v -H "Host: localhost" --no-tls-check --quic-version=h3-29 https://localhost:8443/large_file.zip

Observe the connection establishment and transfer times. You’ll notice HTTP/3 often establishes connections faster, especially under lossy conditions.

The core problem HTTP/3 solves is TCP’s Head-of-Line (HOL) blocking. In TCP, if a packet is lost, all subsequent packets in that stream, and even across different streams multiplexed over the same connection (in HTTP/2), are blocked until the lost packet is retransmitted. HTTP/3 moves the transport layer to QUIC, which runs over UDP. QUIC implements its own stream multiplexing. When a packet is lost in QUIC, it only blocks the specific stream that packet belonged to. Other streams can continue to make progress.

Here’s a breakdown of the key components:

  • QUIC (Quick UDP Internet Connections): This is the transport protocol. It’s built on UDP and provides reliable, ordered delivery of streams. It also handles encryption (TLS 1.3 by default) and connection migration.
  • HTTP/3: This is the application protocol. It runs over QUIC. It reuses many of the concepts from HTTP/2 (like headers, request/response semantics) but adapts them to QUIC’s stream-based model.
  • UDP: The underlying transport. UDP is connectionless and doesn’t guarantee delivery or order. QUIC adds these guarantees on top.

The benefit of UDP is that it avoids the TCP handshake, which can involve multiple round trips. QUIC, with TLS 1.3, can often achieve 0-RTT or 1-RTT connection establishment. This is a significant win for latency, especially on high-latency networks.

Let’s simulate a lossy network with tc. We’ll add 2% packet loss and 100ms latency.

# On the server or a network hop between client and server
sudo tc qdisc add dev eth0 root netem delay 100ms loss 2%

Now, re-run the nghttp client commands. You should see a more pronounced difference in performance, particularly for the larger file, where HTTP/2’s HOL blocking becomes more apparent.

The most surprising thing about HTTP/3 is how much of its perceived performance gain comes from avoiding TCP’s inherent limitations rather than a fundamentally new way of structuring HTTP requests themselves. The multiplexing and stream management are now handled by QUIC, which is a separate layer below HTTP/3. This allows for independent stream progress, meaning a lost packet on one stream doesn’t halt others, a major departure from TCP’s behavior.

The quic-version flag in nghttp is important because QUIC is still evolving. Different versions exist (e.g., h3-29, h3-30). Ensure your client and server agree on a supported version. For production, you’d typically use a well-established version like h3-29.

The next challenge you’ll face is understanding QUIC’s connection migration and how it affects long-lived connections and mobile clients.

Want structured learning?

Take the full Http3 course →