We Rebuilt TCP on Top of UDP and Called It QUIC

QUIC is reliable, ordered, congestion-controlled, and encrypted — everything UDP refuses to be. So why build it on UDP? Because UDP was the only new-protocol-shaped hole left in the internet's plumbing.

Here’s a thing that should bother you more than it does. QUIC — the transport underneath HTTP/3, the protocol now carrying a serious slice of the web — is reliable. It retransmits lost packets. It delivers data in order. It does congestion control. It encrypts everything. That’s the full TCP feature list, and then some.

And it runs on top of UDP. The transport whose entire reason for existing is that it does none of those things.

So we took the connectionless, unreliable, fire-and-forget datagram protocol, and we rebuilt connections and reliability and ordering on top of it, in userspace. If you described that plan to a network engineer in 2005 they’d have asked what you were trying to prove. Turns out it was the only sane move left. And the reason why is more interesting than the protocol itself.

You can’t fix TCP

The honest framing of QUIC is that it’s the transport improvement we couldn’t make to TCP, smuggled past the things that wouldn’t let us make it.

TCP lives in the kernel. To change how TCP behaves on the wire, you need new kernel code on every operating system, and then you need to wait years for people to actually upgrade. That alone would be slow. But it’s not even the real wall.

The real wall is the middleboxes. Every NAT, firewall, load balancer, and “transparent” optimizer between you and the server has spent thirty years learning to read TCP. They inspect sequence numbers. They rewrite options. Some helpfully “fix up” packets in ways the spec never sanctioned. So when you ship a new TCP feature, a meaningful fraction of paths on the internet see a TCP packet that looks slightly wrong and quietly drop or mangle it. The protocol has been parsed so thoroughly by so many boxes that it can no longer evolve. There’s a word for this: ossification. TCP is fossilized by its own success.

UDP, by contrast, is a near-empty envelope. Source port, destination port, length, checksum, done. Middleboxes pass it through because there’s nothing in there for them to get clever about. It is, functionally, the only new-protocol-shaped hole left in the network. You can’t get a genuinely new IP protocol number through the internet’s plumbing anymore — too many boxes block anything that isn’t TCP or UDP. So if you want to deploy a new transport this decade, you wrap it in UDP and you call the UDP payload your real header.

QUIC’s designers took the lesson one step further. They encrypted almost the entire transport header. Not just the payload — the transport machinery itself, the part TCP exposes in cleartext for middleboxes to meddle with. The middleboxes can see a UDP packet going somewhere. They can’t see, and therefore can’t ossify, anything inside. QUIC isn’t just deployable today. It deliberately hid its own internals so it stays deployable tomorrow.

The actual problem it solves

Underneath the deployment cleverness is a real performance bug, and it has a name: head-of-line blocking.

HTTP/2 was supposed to kill it. Instead of HTTP/1.1’s one-request-per-connection juggling, HTTP/2 multiplexed many independent streams over a single TCP connection — images, scripts, the HTML, all interleaved on one socket. Great idea. Except TCP underneath has exactly one job and takes it literally: deliver every byte in order. When a single packet goes missing, TCP refuses to hand the application anything that arrived after it until the retransmission shows up. Everything queues behind the gap.

So one lost packet on one stream freezes every other stream sharing that connection. The image that dropped a packet stalls the stylesheet that arrived perfectly. HTTP/2 didn’t kill head-of-line blocking — it pushed it down from the application into the transport, where it was arguably worse, because now every stream was hostage to the same socket.

QUIC fixes this at the only layer where it can be fixed. It gives every stream its own independent delivery context. Lose a packet in stream 3, and streams 1, 2, and 4 keep flowing — QUIC knows they’re separate, because QUIC, unlike TCP, was designed knowing about streams in the first place. That’s the headline win, and it’s a real one.

The rest of the package is what you’d build if you got to start over. TLS 1.3 isn’t bolted on, it’s baked in — there is no unencrypted QUIC (RFC 9001). The handshake folds the crypto and transport setup together, so a new connection is up in one round trip, and a resumed one can send data in zero. And because a QUIC connection is identified by a connection ID rather than the source IP and port, it survives you walking out of the café — your phone hops from Wi-Fi to cellular, the addresses change completely, and the connection just continues. TCP would have died at the doorway.

The standards landed in 2021: RFC 9000 for the transport, 9001 for the TLS integration, 9002 for loss detection. HTTP/3 — HTTP’s familiar semantics mapped onto QUIC — followed as RFC 9114 in 2022. Google had been running an earlier version across its own services and Chrome for years before that, which is how a protocol this ambitious arrived already battle-tested.

So why hasn’t it eaten everything?

Here’s where the story stops being a victory lap.

QUIC climbed fast and then flattened out. By request share it peaked somewhere in the high twenties percent and has drifted back to roughly a fifth of traffic — not nothing, not the takeover the hype promised either. And the reason for the plateau is the most quietly devastating part of the whole saga.

In 2024 a paper landed at the ACM Web Conference with the title “QUIC is not Quick Enough over Fast Internet.” On fast links, QUIC throughput came in up to 45% lower than plain old HTTP/2 over TCP. Same browsers, same files, dramatically slower. And it got worse as the link got faster.

The root cause is the exact thing that made QUIC possible. QUIC runs in userspace. Every packet, every acknowledgement, has to cross from the kernel up into application code to get processed, instead of riding the hyper-optimized in-kernel path that TCP has had hammered on for forty years. On a slow or lossy mobile link, that overhead disappears into the noise — the network is the bottleneck, not your CPU. But on a clean gigabit fiber connection, the network stops being the bottleneck and the receiver does, because it simply can’t shovel packets through userspace fast enough. The flexibility that let us ship QUIC at all is the flexibility that makes it expensive to run.

So look at where QUIC adoption is actually highest, and it’s not the fiber-rich markets you’d assume. It’s the regions where median bandwidth is lower and links are lossier — exactly the conditions where killing head-of-line blocking matters most and userspace overhead matters least. The protocol is winning precisely where TCP hurt the most, and losing where TCP was already fine. Both of those are the same fact seen from two ends.

We didn’t replace TCP. We built a second TCP — in userspace, on UDP, encrypted against the meddling boxes that wouldn’t let us touch the first one — for the networks the original was bad at. And in doing it we found out the kernel had been quietly earning its keep the whole time.

Continue the conversation

← Back to Blog