HTTP/3 moves the web off TCP and onto QUIC, a transport built on UDP with encryption welded into the handshake. It fixes head-of-line blocking properly, survives a phone switching from Wi-Fi to cellular mid-request, and shaves a round trip off connection setup. It is also, on a fast wired connection, measurably slower than the HTTP/2 stack it replaces - for reasons that have nothing to do with the protocol design.
Both of those things are true, and most writing about HTTP/3 only tells you the first. This piece covers how QUIC actually works, what it genuinely improves, the receiver-side CPU problem that has stalled its adoption curve, how to deploy it, and how to tell whether it helped you.
The problem QUIC was built to solve
HTTP/2 introduced multiplexing: many concurrent requests over one connection, no more queueing six at a time. It worked, and it moved the bottleneck rather than removing it.
Head-of-line blocking, one layer down
TCP delivers a single ordered byte stream. It does not know that your connection carries twelve independent HTTP requests - it sees one sequence of bytes that must arrive in order. Lose one packet belonging to a CSS file, and TCP holds back everything received after it, including complete packets belonging to a JavaScript file that has nothing to do with the loss.
| Layer | HTTP/1.1 | HTTP/2 | HTTP/3 |
|---|---|---|---|
| Application multiplexing | None - one request per connection | Yes | Yes |
| Transport awareness of streams | N/A | None - TCP sees one byte stream | QUIC tracks streams itself |
| Effect of one lost packet | Blocks that connection | Blocks every stream on the connection | Blocks only the affected stream |
Handshakes cost round trips
Opening a TLS-secured HTTP/2 connection means a TCP three-way handshake, then a TLS handshake, then your request. That is typically two to three round trips before a single byte of application data moves. On a 200 ms mobile link, you have spent half a second on introductions.
Connections are pinned to an IP address
A TCP connection is identified by the four-tuple of source IP, source port, destination IP and destination port. Change any of them and it is a different connection. When a phone walks out of Wi-Fi range onto cellular, the source IP changes, every open connection dies, and everything reconnects from scratch.
The middlebox problem
TCP's headers are unencrypted, so three decades of firewalls, load balancers, NAT boxes and traffic shapers have been built to read and modify them. That ossification means changing TCP itself is effectively impossible: deploy a new TCP option and some middlebox somewhere will drop your packets. This is why QUIC was built on UDP rather than as TCP v2 - not because UDP is better, but because it is the only transport the internet will let you innovate on.
How QUIC works
QUIC is a transport protocol that runs in user space on top of UDP. It provides what TCP provides - reliability, ordering within a stream, congestion control, flow control - plus encryption, plus stream multiplexing that the transport actually understands.
HTTP/2 stack HTTP/3 stack
───────────────── ─────────────────
HTTP/2 HTTP/3
TLS 1.3 QUIC (streams, reliability, TLS 1.3 built in)
TCP UDP
IP IP
Kernel handles TCP. User space handles QUIC.
That last line explains most of this article.Streams are first-class
A QUIC connection carries many independent streams, each with its own delivery guarantees. Loss on stream 4 delays stream 4. Streams 0 through 3 keep flowing. This is the head-of-line fix, and it only works because the transport itself knows the streams exist.
- Streams are identified by a 62-bit variable-length integer, so you will not run out.
- The low bits of the ID encode whether the stream is client- or server-initiated, and whether it is bidirectional or unidirectional.
- Flow control operates per stream and per connection, so one greedy stream cannot starve the rest.
- A stream can be reset independently - cancelling one request does not disturb the others.
The handshake is one round trip
TLS 1.3 is not layered on top of QUIC; it is integrated into it. The cryptographic handshake and the transport handshake happen in the same flight of packets, so a new connection reaches application data in one round trip rather than two or three.
TCP + TLS 1.3 (HTTP/2) QUIC (HTTP/3)
────────────────────── ─────────────
SYN → Initial + ClientHello →
← SYN-ACK ← Initial + ServerHello + …
ACK + ClientHello → HTTP request →
← ServerHello …
HTTP request →
~2–3 RTT to first byte ~1 RTT to first byte0-RTT resumption, and its sharp edge
On a repeat connection to a server you have spoken to before, the client can send application data in its very first packet using a cached key. Zero round trips before the request goes out.
Connection IDs, and why your phone stops stalling
A QUIC connection is identified by a Connection ID carried in the packet header, not by the IP four-tuple. When the client's address changes, it keeps sending packets with the same Connection ID and the server recognises the connection and continues. No new handshake, no dropped requests, no spinner.
This is the single most underrated feature of QUIC, and the one your users will actually feel. Walking out of a café onto the street mid-upload used to mean starting over. Endpoints also rotate through a pool of Connection IDs precisely so that observers on the path cannot use the ID to track a device across networks.
Everything is encrypted, including the transport
TCP exposes its sequence numbers, flags and options to anything on the path. QUIC encrypts almost the entire packet, including most of the header. Only the bare minimum a router needs stays visible.
Two consequences follow. Middleboxes cannot ossify what they cannot read, so QUIC can evolve in a way TCP cannot. And your existing network appliances - the ones doing deep packet inspection, traffic shaping and protocol-aware monitoring - go blind. Whether that is a feature or a problem depends on which side of the firewall you sit.
QPACK, and a deliberate compromise
HTTP/2 compressed headers with HPACK, which relies on a shared dynamic table updated in strict order - an assumption QUIC's out-of-order streams break. QPACK is the redesign: it keeps most of the compression while tolerating streams arriving in any order, at the cost of some encoder complexity and occasional blocking when a reference outruns its definition.
Amplification protection
Any UDP protocol risks becoming a reflection attack vector: spoof a victim's source address, send a small request, and the server floods the victim with a large response. QUIC's answer is a hard rule - a server must not send more than three times the bytes it has received from an address it has not yet validated. The DNS and NTP amplification problems were designed out from the start.
The part most articles leave out
QUIC is better on paper and better on bad networks. On a fast, low-loss wired connection, the measured result is often worse than HTTP/2 - and the gap widens as bandwidth increases.
The measurement
The ACM Web Conference 2024 paper *QUIC is not Quick Enough over Fast Internet* is the reference here. Across Chrome, Edge, Firefox and Opera, on desktop and mobile, over both wired broadband and cellular, the authors measured the UDP + QUIC + HTTP/3 stack delivering up to 45.2% lower data rate than TCP + TLS + HTTP/2 on high-bandwidth links. Video streaming showed up to 9.8% bitrate reduction; page load times were around 3% longer.
Why - and it is not the protocol's fault
The root cause is receiver-side CPU, and it comes from QUIC living in user space while TCP lives in the kernel with two decades of hardware and kernel optimisation behind it.
| Mechanism | TCP | QUIC |
|---|---|---|
| Packet coalescing on receive | Generic Receive Offload, widely deployed, often in the NIC | UDP GRO exists but was not used by the implementations measured |
| Packets crossing into the stack | ~15K netif_receive_skb calls for the measured download | ~231K calls for the same download |
| Acknowledgement generation | In-kernel, with delayed-ACK batching | User space, per-packet, no equivalent batching |
| CPU spent on ACKs alone | Negligible | Roughly 3 seconds of wall clock in the paper's 1 GB test |
| Sending many packets at once | Segmentation offload, mature | sendmmsg on Linux; no public equivalent on macOS |
This is fixable. UDP GRO can be enabled and is being enabled. ACK batching can be implemented. Receive processing can be spread across cores. But the fixes are not universally deployed as of 2026, and until they are, the honest statement is that HTTP/3's benefit depends heavily on your users' network conditions.
Which explains the plateau
HTTP/3 adoption climbed quickly to roughly a third of traffic and then flattened. The usual explanation is operational inertia. The more likely explanation is that the protocol wins clearly on lossy, high-latency, mobile networks and wins ambiguously or loses on fast fixed-line connections - so the aggregate benefit stopped compounding once the easy wins were taken.
Adoption also skews toward mobile-first markets - Italy, Brazil and India lead in several measurements - which is exactly what you would expect if the benefit tracks network quality rather than fashion.
Where HTTP/3 genuinely wins
| Condition | Why QUIC helps |
|---|---|
| High packet loss | Loss affects one stream instead of stalling the whole connection |
| High latency | One fewer round trip on connect, zero on resumption |
| Mobile and changing networks | Connection migration survives a Wi-Fi to cellular handover |
| Many small resources | True multiplexing without transport-layer coupling between them |
| Repeat visitors | 0-RTT resumption for idempotent requests |
| Long-lived connections | Migration and stream independence compound over the connection's life |
| Restrictive or lossy networks abroad | Aggregate effect of all of the above on the worst connections you serve |
Notice the shape of that list. Every entry describes a network that is imperfect. If your users are on fibre in a major city, the honest expected gain is small. If a meaningful share are on mobile data in Dhaka, Jakarta or São Paulo, it is not.
Deploying it
The easy path
If you are behind Cloudflare, Fastly, CloudFront or most other major CDNs, HTTP/3 is either already on or is a toggle. The CDN terminates QUIC at the edge and speaks whatever your origin speaks. For most sites this is the entire deployment.
nginx
server {
# HTTP/2 over TCP, kept as the fallback
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
# HTTP/3 over QUIC on UDP 443
listen 443 quic reuseport;
listen [::]:443 quic reuseport;
ssl_certificate /etc/ssl/example.com.pem;
ssl_certificate_key /etc/ssl/example.com.key;
ssl_protocols TLSv1.3;
# Tells clients arriving over TCP that HTTP/3 is available
add_header Alt-Svc 'h3=":443"; ma=86400' always;
location / {
proxy_pass http://app_upstream;
}
}reuseport should appear on only one listen quic directive per address and port across your whole configuration.Requires nginx 1.25.0 or later built with QUIC support. Verify before you edit anything: nginx -V 2>&1 | grep -o with-http_v3_module.
Caddy
example.com {
reverse_proxy localhost:3000
}The firewall rule people forget
# QUIC needs UDP 443 open in both directions
sudo ufw allow 443/udp
# AWS security group
aws ec2 authorize-security-group-ingress \
--group-id sg-xxxxxxxx \
--protocol udp --port 443 --cidr 0.0.0.0/0Discovery: Alt-Svc and HTTPS records
A browser's first connection to you is over TCP, because it has no way to know you speak QUIC. The Alt-Svc response header tells it, and it uses HTTP/3 from the next request onward. That means the very first page load never benefits.
DNS HTTPS records (also called SVCB) close that gap by advertising HTTP/3 support during resolution, so even a first-time visitor can go straight to QUIC.
example.com. 3600 IN HTTPS 1 . alpn="h3,h2" port=443Load balancers and proxies
This is where self-hosted deployments actually get difficult. A conventional L7 load balancer cannot inspect QUIC, because the headers are encrypted. You need a proxy that terminates QUIC itself - recent Envoy and HAProxy both do - or you terminate at the CDN and speak HTTP/1.1 or HTTP/2 to your origin, which is what most people end up doing.
Verifying it actually works
# curl, if built with HTTP/3 support
curl -I --http3 https://example.com
curl -sI --http3 https://example.com | head -1 # expect: HTTP/3 200
# check the Alt-Svc header is being sent over TCP
curl -sI https://example.com | grep -i alt-svc
# confirm UDP 443 is reachable from outside
nc -zvu example.com 443
# check for a DNS HTTPS record
dig example.com HTTPS +shortcurl --http3 needs a build linked against a QUIC-capable TLS library. curl --version will list HTTP3 among its features if so.In the browser, open DevTools, go to the Network panel, right-click the column headers and enable Protocol. h3 means QUIC. Expect the first request of a cold visit to show h2 and subsequent ones to show h3 unless you have published an HTTPS record.
What to log
log_format quiclog '$remote_addr $server_protocol $status '
'$request_time $body_bytes_sent "$request"';
access_log /var/log/nginx/access.log quiclog;$server_protocol reports HTTP/3.0 for QUIC requests. Split your latency percentiles by protocol - aggregate numbers will hide whichever effect is real.- Track the ratio of HTTP/3 to HTTP/2 requests. A sudden drop usually means a network path started blocking UDP.
- Compare p50, p95 and p99 latency by protocol rather than in aggregate. The whole point is that HTTP/3's benefit is unevenly distributed.
- Segment by client type. Mobile and desktop should show different results - if they do not, something is wrong with your measurement.
- Watch CPU on your edge servers. QUIC's encryption and packet handling cost more than TCP's, which is the trade you are making.
- Keep HTTP/2 enabled indefinitely. HTTP/3 is additive, and fallback is a feature.
What QUIC enables beyond page loads
The transport is more interesting than the HTTP mapping on top of it, and this is where the next few years of work actually are.
| Technology | Standard | What it does |
|---|---|---|
| QUIC Datagrams | RFC 9221 | Unreliable delivery inside a reliable connection - real-time media without a second socket |
| WebTransport | W3C, over HTTP/3 | Bidirectional streams and datagrams from the browser, with none of WebRTC's signalling overhead |
| Capsule Protocol | RFC 9297 | The framing that lets tunnelled protocols share an HTTP/3 stream |
| CONNECT-UDP | RFC 9298 | Proxy arbitrary UDP through HTTP/3 |
| CONNECT-IP | RFC 9484 | Tunnel whole IP packets - VPN behaviour over standard HTTP |
| DNS over QUIC | RFC 9250 | Encrypted DNS without TCP's handshake cost |
| QUIC version 2 | RFC 9369 | Near-identical to version 1, published to exercise version negotiation and fight ossification |
| Multipath QUIC | Working Group draft | One connection across Wi-Fi and cellular simultaneously - still a draft, not yet an RFC |
WebTransport is the piece most application developers should care about. It gives you reliable streams and unreliable datagrams from JavaScript over the same QUIC connection your page already uses - the right primitive for multiplayer state, live telemetry and low-latency media, and far less machinery than WebRTC.
const transport = new WebTransport('https://example.com/session')
await transport.ready
// Unreliable, unordered - the right choice for positional updates
const writer = transport.datagrams.writable.getWriter()
await writer.write(new TextEncoder().encode('{"x":12,"y":40}'))
// Reliable, ordered - for anything that must arrive
const stream = await transport.createBidirectionalStream()
const streamWriter = stream.writable.getWriter()
await streamWriter.write(new TextEncoder().encode('join:room-7'))The costs
CPU
Expect meaningfully higher CPU per request than TCP with TLS - commonly cited estimates land in the region of 10–20% more, and the measured overhead is worse on high-bandwidth transfers. TCP benefits from NIC offload and kernel optimisation that QUIC's user-space implementations are still catching up to. At CDN scale this is a real hardware line item.
Observability regresses
Your packet captures become opaque. Wireshark can decode QUIC only if you export TLS session keys, which is fine on a laptop and awkward in production. Network appliances that understood TCP see encrypted UDP. Plan for qlog - the structured logging format most QUIC libraries emit - rather than assuming your existing tooling will carry over.
# Wireshark can decrypt QUIC if the client exports its keys
export SSLKEYLOGFILE=/tmp/keys.log
google-chrome --user-data-dir=/tmp/chrome-quic
# then in Wireshark:
# Preferences → Protocols → TLS → (Pre)-Master-Secret log filenameUDP is treated as second-class
Corporate firewalls, some mobile carriers and plenty of public Wi-Fi block or throttle UDP 443 on the reasonable-sounding grounds that legitimate web traffic uses TCP. Clients fall back cleanly, so nothing breaks. It does mean your HTTP/3 numbers will be lower than your configuration implies, and that the users on the most restrictive networks - often the ones who would benefit most - get it least.
Operational maturity
TCP has forty years of accumulated operational knowledge, tooling and tuning folklore. QUIC has a few. When something behaves strangely at 3am, the search results are thinner and the people who have seen it before are fewer.
0-RTT needs application awareness
Enabling ssl_early_data at the server is a one-line change. Making it safe requires your application to check the Early-Data header and refuse non-idempotent requests. Skipping the second half is how you turn a latency optimisation into a duplicated payment.
Should you enable it?
Yes, without much thought, when
- You are behind a CDN that offers it. It is a toggle and the fallback is safe.
- A meaningful share of your users are on mobile networks or in regions with poor connectivity.
- Your product involves long-lived connections where migration matters - uploads, streaming, real-time collaboration.
- You care about Core Web Vitals and your users are not all on fibre.
Think harder when
- You self-host and your load balancer cannot terminate QUIC. That is a real infrastructure project, not a config change.
- Your traffic is overwhelmingly high-bandwidth fixed-line, where the measured benefit may be negative.
- You are CPU-constrained at the edge and running close to capacity.
- Your compliance or security posture depends on deep packet inspection that QUIC will blind.
- Your service is internal, on a fast reliable network, where none of QUIC's advantages apply.
Never
- Disable HTTP/2 when you enable HTTP/3. They coexist by design, and the fallback path is what makes QUIC deployable at all.
- Enable 0-RTT without handling the
Early-Dataheader in your application. - Quote a supported-websites adoption figure as though it were a traffic figure.
Verdict
QUIC is a genuinely better transport than TCP for the internet most people actually use - lossy, high-latency, mobile, changing networks. Connection migration alone justifies it for anything a phone talks to. The head-of-line fix is real. The handshake saving is real. The encryption-by-default is the right default.
What it is not is a free upgrade that makes everything faster. On fast fixed-line connections the current implementations lose to HTTP/2, because TCP has decades of kernel and NIC optimisation that user-space QUIC has not finished replicating. That gap is an engineering problem with known fixes, and it is not fixed yet.
Enable it, keep HTTP/2, and measure your own percentiles split by protocol. Anyone quoting you a single speed-up number has not looked at their own data.
For most teams the decision is easy because the CDN makes it for you, the fallback is safe, and the users who benefit most are the ones you can least afford to serve badly. For anyone self-hosting at scale, it is a real project with a real CPU bill and an observability cost - worth doing, worth planning, not worth pretending is trivial.
Sources
- RFC 9000 - QUIC, the transport protocol
- RFC 9001 and RFC 9002 - TLS integration, loss detection and congestion control
- RFC 9114 and RFC 9204 - HTTP/3 and QPACK
- RFC 9221, RFC 9297, RFC 9298, RFC 9484 - datagrams, capsules and tunnelling
- RFC 9369 - QUIC version 2 and the anti-ossification rationale
- Multipath extension draft - QUIC Working Group, still in progress
- QUIC is not Quick Enough over Fast Internet - the receiver-side overhead measurements
- QUIC Working Group - specifications, drafts and implementation list



