Senior 6 min · March 06, 2026

HTTP/2 vs HTTP/3 — Packet Loss Collapses Streams

2% packet loss stalls all HTTP/2 streams via TCP head-of-line blocking.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • HTTP/2 multiplexes multiple streams over one TCP connection, solving HTTP/1.1 head-of-line blocking
  • HTTP/3 replaces TCP with QUIC over UDP, eliminating TCP head-of-line blocking entirely
  • Key difference: HTTP/2 still suffers from TCP packet loss stalling all streams; HTTP/3 isolates each stream
  • Performance: HTTP/3 cuts connection setup from 3+ round trips (TCP+TLS) to 0–1 with QUIC 0-RTT
  • Production insight: HTTP/2 can actually be slower than HTTP/1.1 on high-packet-loss mobile networks
  • Biggest mistake: assuming HTTP/3 always outperforms HTTP/2 — it depends on network conditions and server load
Plain-English First

Imagine ordering food at a restaurant. HTTP/1.1 is like having one waiter who takes your order, runs to the kitchen, waits for every dish, and only then comes back — one trip at a time. HTTP/2 is like that same waiter carrying multiple dishes at once on a big tray. HTTP/3 is like switching from a slow, congested road to a helicopter — it changes the entire transport layer so delays in one order never slow down anyone else's food.

Every time you open a webpage, your browser and a server are having a conversation. The rules of that conversation are defined by HTTP — HyperText Transfer Protocol. For most of the web's history, that conversation was painfully inefficient: one request at a time, over a single lane, with no ability to multitask. As web pages ballooned from a few kilobytes to megabytes of JavaScript, CSS, images, and fonts, those inefficiencies became a serious bottleneck. HTTP/2 and HTTP/3 exist because the old rules simply couldn't keep up with the modern web.

HTTP/1.1 solved the problem of a static web. But today's pages routinely make 80–200 individual requests to load a single page. HTTP/1.1 handles this with something called connection pooling — browsers open 6 connections per domain and pipeline requests awkwardly. This creates head-of-line blocking: if request #3 stalls, requests #4 and #5 wait behind it even if they're ready. HTTP/2 attacked this with multiplexing over a single TCP connection. HTTP/3 went further and replaced TCP itself with a protocol built for the unreliable, lossy networks that mobile users live on every day.

After reading this article, you'll understand exactly why HTTP/2 and HTTP/3 were designed the way they were, what problems each one solves (and which ones it doesn't), how to verify which protocol your server is using, and how to write a Node.js HTTP/2 server from scratch. You'll also walk into any system design interview and be able to explain the difference between TCP head-of-line blocking and HTTP head-of-line blocking — a distinction that trips up even experienced engineers.

What Makes HTTP/2 Faster Than HTTP/1.1?

HTTP/2 introduced two fundamental changes: binary framing and multiplexing. Instead of sending plaintext headers and body separately, everything is broken into frames (HEADERS, DATA, RST_STREAM, etc.) over a single TCP connection. This allows the client to send multiple requests in parallel without waiting for responses — something HTTP/1.1 could only approximate with multiple connections or broken pipelining.

But multiplexing isn't free. The server must manage a stream state machine, apply flow control per stream, and compress headers using HPACK. HPACK is a static/dynamic table that avoids retransmitting the same header names (like :method: GET) — a significant win because average header size drops from ~800 bytes to ~50 bytes after a few requests.

The real enabler is the single connection. With HTTP/1.1, browsers open up to 6 parallel connections per domain. Each connection requires a TCP handshake (1 RTT) and TLS handshake (1–2 RTTs). HTTP/2 uses one connection, so one initial handshake covers all subsequent requests. At scale, this saves precious milliseconds on heavy pages.

io/thecodeforge/http2demo/Http2Client.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package io.thecodeforge.http2demo;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

public class Http2Client {
    public static void main(String[] args) throws Exception {
        HttpClient client = HttpClient.newBuilder()
                .version(HttpClient.Version.HTTP_2)
                .connectTimeout(Duration.ofSeconds(5))
                .build();

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://http2.akamai.com/demo/h2_demo_frame.html"))
                .GET()
                .build();

        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
        System.out.println("Protocol: " + response.version());
        System.out.println("Status: " + response.statusCode());
        System.out.println("Headers: " + response.headers().map());
    }
}
Output
Protocol: HTTP_2
Status: 200
Headers: {content-type=[text/html], ...}
Think of it like a highway lane
  • Binary framing: each request/response is split into frames that interleave on the wire
  • Multiplexing: streams are independent — one slow resource doesn't block others (at HTTP level)
  • HPACK compression: headers are stored in a table, only differences are sent
  • Single connection: reduces handshake overhead and congestion window warm-up
Production Insight
HTTP/2's single connection is a double-edged sword. Under packet loss (>2%), TCP's congestion control throttles the entire connection, stalling all streams. You'll see RST_STREAM frames from the client as timeouts accumulate.
Always monitor retransmit rate on the TCP connection. A single retransmitted packet can cause a 2-second stall for every stream.
Rule: If your app serves lossy mobile networks, HTTP/2 may be slower than HTTP/1.1 with multiple connections.
Key Takeaway
HTTP/2's multiplexing solves HTTP head-of-line blocking but introduces TCP head-of-line blocking.
Know which one you're debugging: HTTP-level (request ordering) or TCP-level (packet loss stalls everything).

TCP Head-of-Line Blocking: Why HTTP/2 Fails on Lossy Networks

Here's the dirty secret: HTTP/2's multiplexing runs over a single TCP socket. TCP is a reliable, ordered protocol. If packet #5 is lost, all packets #6, #7, #8 sit in the receive buffer waiting for #5 to be retransmitted — even if they belong to different HTTP streams. This is TCP head-of-line (HOL) blocking.

In a lab with 0% loss, you'll see beautiful parallelism. Throw in 2% packet loss — typical for cellular networks — and the TCP congestion window collapses. The sender stops sending new data until the lost packet is acknowledged. Every multiplexed stream behind that lost packet freezes.

Akamai's real-world data shows that for users with 2%–3% packet loss, HTTP/2 can be 1.5× to 2× slower than HTTP/1.1 (which uses multiple TCP connections, so only one connection starves). This caught many engineers off guard when they migrated to HTTP/2 expecting universal improvement.

The fix? Either restrict HTTP/2 to low-loss networks (datacenter, fiber) or move to a transport that doesn't require total ordering — which is exactly what QUIC does.

Production Trap: Don't assume HTTP/2 always beats HTTP/1.1
Test under realistic network conditions. Use Chrome DevTools network throttling (Regular 3G, 4G, 3G Fast) and measure Time to Interactive. If you see regression, consider falling back to HTTP/1.1 for those conditions or enabling multiple connections.
Production Insight
Cloudflare observed that HTTP/2 over Qualcomm's LTE modems had 20% higher retransmit rates than HTTP/1.1. The single connection made the congestion control more aggressive.
Debug by capturing tcpdump on the server and counting duplicate ACKs — they indicate packet loss and trigger fast retransmit.
Rule: Never benchmark HTTP/2 on localhost. Always use a network impairment tool like Comcast (Linux) or a tcp_wrapper to simulate packet loss.
Key Takeaway
TCP HOL blocking is the silent killer of HTTP/2 performance in the wild.
If your users are mobile, you need HTTP/3 (QUIC) or a fallback strategy.

How QUIC and HTTP/3 Eliminate TCP Head-of-Line Blocking

QUIC (Quick UDP Internet Connections), originally designed by Google, replaces TCP and TLS with a single transport layer over UDP. It's not just HTTP/2 over UDP — it's a fundamentally different transport that provides:

  • Stream independence: Each HTTP request/response is mapped to a QUIC stream. If a packet for stream #3 is lost, only stream #3 blocks. Streams #4 and #5 continue delivering data as soon as their packets arrive. This solves TCP HOL blocking at the transport level.
  • 0-RTT connection establishment: If you've connected to a server before, you can send data immediately in the very first packet (0-RTT). The cryptographic state is cached from the previous session. This cuts latency from 3+ round trips (TCP + TLS 1.3) to 0–1.
  • Built-in encryption: TLS 1.3 is integrated into QUIC handshake — there's no separate TLS layer. All QUIC packets are encrypted except a few early bytes used for routing.
  • Connection migration: Your QUIC connection survives IP address changes (e.g., switching from Wi-Fi to cellular). The server identifies you via a Connection ID, not IP:port. No timeout, no re-handshake.

QUIC runs over UDP, which is a unencumbered by TCP's in-order delivery and head-of-line constraints. But UDP introduces new challenges: firewalls often block it (or rate-limit it), and load balancers need QUIC-specific support.

check_quic.shCURL
1
2
3
4
5
#!/bin/bash
# Check if a server supports HTTP/3 (QUIC)
# Requires curl built with HTTP/3 support (--http3 flag)
curl --http3 -sI https://www.google.com 2>&1 | head -5
# Look for 'alt-svc: h3="..."' in response headers
Output
HTTP/3 200
alt-svc: h3=":443";ma=86400,h3-29=":443";ma=86400
QUIC is a transport protocol, not just HTTP
QUIC was designed to carry HTTP (hence HTTP/3), but it's becoming a generic transport. Google's internal RPC system, gRPC, now supports QUIC as a transport option. This means the benefits extend beyond web browsing.
Production Insight
Switching to QUIC can reduce page load times by 10–30% on mobile networks. Facebook reported a 20% reduction in overall latency after enabling QUIC for their mobile app.
However, QUIC's UDP-based flow control is complex. Misconfigured server limits can cause connection stalls that are hard to debug because packet drops happen silently.
Rule: Ensure your load balancer and firewall support QUIC (UDP/443). If they don't, connections degrade to HTTP/2 or even HTTP/1.1.
Key Takeaway
QUIC eliminates TCP HOL blocking by giving each stream its own reliable sub-transport.
But the gain depends on how much packet loss your users experience — in zero-loss environments, HTTP/2 (over TCP) can match QUIC because TCP overhead is minimal.

Real-World Performance: HTTP/2 vs HTTP/3 with Numbers

You can't trust engineering claims without numbers. Here's what production data shows:

  • Connection establishment: HTTP/2 over TCP+TLS 1.3 takes 2 RTTs. HTTP/3 with 0-RTT takes 0 RTT for returning users. That's a 100% reduction in handshake latency.
  • Packet loss impact: At 2% loss, HTTP/2 throughput drops by as much as 50% due to TCP congestion window collapse. HTTP/3 with QUIC's independent streams stays above 80% throughput for the same loss rate.
  • CPU usage: HTTP/2 is more CPU-intensive server-side than HTTP/1.1 because of HPACK compression and stream management. HTTP/3 further increases CPU overhead due to encryption at the transport layer (every packet must be encrypted/decrypted). Cloudflare measured a 15–20% increase in CPU per connection for HTTP/3 compared to HTTP/2.
  • Bandwidth overhead: QUIC's connection ID and other fields add about 10 bytes per packet versus TCP's minimal header. Over a long video stream, this is negligible. But for many small requests (a typical web page does 100+ requests), the overhead adds up. Google's studies show QUIC adds ~2% more bytes for typical web traffic.

The trade-off: HTTP/3 wins on lossy, high-latency networks (mobile, satellite). HTTP/2 wins on low-latency, lossless networks (datacenter) where the CPU cost of QUIC encryption isn't justified.

Think of HTTP/3 as a file per stream, not a single conveyor belt
  • Independent streams prevent one loss from blocking unrelated resources
  • 0-RTT reduces connection time for repeat visitors
  • Connection migration eliminates re-handshake on network change
  • Higher CPU cost is the price you pay for these benefits
Production Insight
Benchmarking tip: Never test HTTP/2 vs HTTP/3 using localhost or a LAN connection. You'll see HTTP/2 winning because QUIC's UDP stack adds overhead. Use a network simulator (like tc on Linux) to add 10–50ms latency and 1–3% packet loss for realistic results.
In my team's production environment, we deployed HTTP/3 for image/video CDN and HTTP/2 for API calls that flow through internal data centers. That hybrid approach gave us the best of both worlds.
Key Takeaway
HTTP/3 is not universally faster than HTTP/2.
HTTP/3 wins where latency and loss hurt. HTTP/2 wins where CPU load and infrastructure compatibility dominate.

Migrating to HTTP/3: What You Need in Production

  1. Server support: nginx (since 1.25+ with --with-http_v3_module), Caddy (built-in), Cloudflare, AWS CloudFront, and most modern CDNs support HTTP/3. But your own application server might not — many web frameworks only speak HTTP/1.1/2. You'll need a reverse proxy that terminates HTTP/3 (like nginx or Envoy) and proxies to your app via HTTP/2 or HTTP/1.0.
  2. UDP routing: Load balancers must be configured to pass UDP traffic on port 443. Most cloud LBs (AWS ALB, Google HTTPS LB) now support QUIC. If you're using a self-managed LB (HAProxy, nginx), you need to add a UDP listener.
  3. Firewall rules: Corporate firewalls, security appliances, and some home routers block UDP. Have a fallback: if H3 fails, the browser will retry with H2. Ensure alt-svc header is sent to advertise H3 support.
  4. Monitoring: You need separate metrics for H2 vs H3. Your APM might not differentiate if both use port 443. Use connection_id and protocol_version labels in Prometheus metrics.
  5. Testing: Use curl --http3 and browser dev tools to verify. The easiest way to test end-to-end is with Caddy: it's a single binary with H3 support out of the box.
/etc/nginx/sites-enabled/h3.confNGINX
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Minimum nginx version 1.25 with --with-http_v3_module
server {
    listen 443 quic reuseport;
    listen 443 ssl;
    http2 on;
    http3 on;

    ssl_protocols TLSv1.3;
    ssl_certificate /etc/ssl/certs/example.crt;
    ssl_certificate_key /etc/ssl/private/example.key;

    add_header Alt-Svc 'h3=":443";ma=86400' always;

    location / {
        proxy_pass http://backend:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
    }
}
Don't forget the alt-svc header
Without the Alt-Svc response header, browsers won't attempt HTTP/3 on subsequent requests. Even if your server listens on QUIC, clients will keep using HTTP/2 until they see the advertisement.
Production Insight
We rolled out HTTP/3 to only 5% of users at first, using an nginx configuration that conditionally added Alt-Svc based on a cookie. This let us monitor error rates and CPU usage before full rollout.
The biggest gotcha: some load balancers (like AWS ALB) advertise H3 support but then drop the Connection ID from their logs, making it impossible to correlate client-side performance with server-side QUIC sessions. Log every QUIC connection ID.
Rule: Always have a fallback. If QUIC fails, browsers transparently fall back to H2. Don't break the fallback by blocking Alt-Svc from non-H3 servers.
Key Takeaway
HTTP/3 deployment is a multi-layer change: network, server, and monitoring.
Protect your users with fallback: if H3 fails, H2 should work seamlessly.
● Production incidentPOST-MORTEMseverity: high

HTTP/2 Stream Collapse Under 2% Packet Loss

Symptom
Pages loading slower on mobile networks after HTTP/2 migration. Desktop performance improved, but mobile degraded noticeably.
Assumption
HTTP/2 multiplexing would improve performance across all network conditions.
Root cause
TCP packet loss on mobile networks caused all multiplexed streams to wait for a single retransmitted packet. The sender's congestion window collapsed, stalling every stream simultaneously.
Fix
Deployed an application-layer prioritization scheme: critical resources (CSS, fonts) were sent on streams with higher priority, and the server was configured to not fully fill the TCP window until higher-priority streams completed. Eventually migrated critical flows to HTTP/3 (QUIC) which isolates stream loss.
Key lesson
  • HTTP/2 multiplexing is not immune to TCP head-of-line blocking — it only solves the HTTP-level line blocking.
  • On networks with >1% packet loss, HTTP/2 can be slower than HTTP/1.1 with multiple connections.
  • Always test protocol performance under realistic network conditions, not just low-latency localhost.
Production debug guideDiagnose protocol-level problems before they become user-facing incidents.4 entries
Symptom · 01
Page loads slow on mobile but fast on desktop
Fix
Check network conditions: use Chrome DevTools Network panel with 'Disable cache' and throttling. Look for protocol version (h2 vs h3). If h2, check for high number of PING frames indicating keep-alive issues.
Symptom · 02
Server CPU spikes after HTTP/2 enabled
Fix
HTTP/2 server-side processing (HPACK compression, stream multiplexing) is CPU-intensive. Use perf or async-profiler to check if priority framing or header compression dominates.
Symptom · 03
QUIC connections failing intermittently
Fix
QUIC uses UDP port 443; ensure firewalls/load balancers allow UDP. Use tcpdump to capture UDP traffic, verify QUIC handshake packets (Initial, Handshake).
Symptom · 04
Stream multiplexing causing one slow resource to block others
Fix
In HTTP/2, server must implement proper stream prioritization. Check if your CDN/load balancer respects RFC 7540 priority dependencies. Consider limiting concurrent streams per connection.
★ Quick Debug Cheat Sheet: HTTP/2 & HTTP/3Use these commands and checks the moment you suspect a protocol-related issue.
Check protocol version used by browser
Immediate action
Open DevTools > Network tab, right-click column header > enable Protocol column.
Commands
curl -v --http2 https://example.com | grep -i 'ALPN'
curl -v --http3 https://example.com 2>&1 | grep -i 'QUIC'
Fix now
If h2 not seen, ensure server supports ALPN and h2. If h3 not seen, check UDP routing and server-side QUIC support.
HTTP/2 connection collapsing under load+
Immediate action
Check nginx error log for 'http2: too many streams' or 'upstream timeouts'.
Commands
sudo tcpdump -i eth0 port 443 -A | grep -E 'GOAWAY|RST_STREAM'
cat /var/log/nginx/access.log | grep 'HTTP/2' | awk '{print $9}' | sort | uniq -c | sort -rn
Fix now
Increase http2_max_concurrent_streams or reduce server timeout values. Consider enabling HTTP/2 connection-level backpressure.
QUIC handshake not completing+
Immediate action
Check if UDP port 443 is open via telnet or nc.
Commands
nc -u -v -z example.com 443</dev/null; echo $?
ss -tulpn | grep :443
Fix now
Ensure load balancers and firewalls allow UDP/443. Many cloud LBs need special QUIC/UDP listener configuration.

Key takeaways

1
HTTP/2 multiplexes over a single TCP connection, solving HTTP-level block but introducing TCP-level block.
2
QUIC (HTTP/3) gives each stream its own reliability, eliminating transport HOL blocking.
3
HTTP/3 wins on lossy, mobile networks; HTTP/2 can still be faster in low-loss data center environments.
4
Never migrate without testing under realistic network conditions with packet loss.
5
Always advertise HTTP/3 via Alt-Svc and provide a fallback to HTTP/2.

Common mistakes to avoid

4 patterns
×

Assuming HTTP/2 is universally faster than HTTP/1.1

Symptom
Production latency increased for mobile users after switching to HTTP/2.
Fix
Test under realistic network conditions with packet loss. Use multiple TCP connections for lossy networks, or deploy HTTP/3.
×

Not enabling Alt-Svc header with HTTP/3

Symptom
Browsers never use HTTP/3 even though server supports QUIC.
Fix
Add add_header Alt-Svc 'h3=":443";ma=86400' always; in your server configuration.
×

Assuming HTTP/2 eliminates all head-of-line blocking

Symptom
One slow API response delays other stream responses on the same connection.
Fix
Implement server-side stream prioritization according to RFC 7540. Use priority=HIGH for critical resources (CSS, fonts).
×

Using HTTP/2 without proper flow control configuration

Symptom
Server crashes or connection timeouts under high concurrency.
Fix
Set SETTINGS_MAX_CONCURRENT_STREAMS (in nginx: http2_max_concurrent_streams) to a safe limit like 128, and configure SETTINGS_INITIAL_WINDOW_SIZE to 1MB.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
Explain the difference between HTTP/2 and HTTP/3 head-of-line blocking.
Q02SENIOR
How does connection migration work in HTTP/3?
Q03SENIOR
Why might you choose to keep HTTP/2 instead of upgrading to HTTP/3?
Q01 of 03SENIOR

Explain the difference between HTTP/2 and HTTP/3 head-of-line blocking.

ANSWER
HTTP/2 solves HTTP-level HOL blocking by multiplexing multiple streams over a single TCP connection. However, it introduces TCP-level HOL blocking: if a TCP packet is lost, all streams wait for its retransmission because TCP guarantees in-order delivery. HTTP/3 uses QUIC over UDP, where each stream has its own reliable sub-transport. A lost packet for one stream does not affect others. This makes HTTP/3 much more resilient on lossy networks like mobile or satellite.
FAQ · 4 QUESTIONS

Frequently Asked Questions

01
Is HTTP/3 the same as QUIC?
02
Does HTTP/3 require TLS?
03
Can I use HTTP/3 with a CDN?
04
Will HTTP/3 reduce my server CPU overhead?
🔥

That's Computer Networks. Mark it forged?

6 min read · try the examples if you haven't

Previous
HTTP and HTTPS Explained
6 / 22 · Computer Networks
Next
DNS — Domain Name System