QUIC is usually summed up with an explanation that’s too simple: HTTP/3 uses UDP because UDP is faster than TCP. Technically, that idea leads to the wrong conclusion. UDP doesn’t provide on its own the guarantees HTTP needs, and QUIC has to build reliability, congestion control, loss recovery, multiplexing, and security on top of it. The decision to use UDP is largely a response to a different problem: TCP works extraordinarily well, but after decades of deployment, it has become very difficult to modify and to roll those changes out across the Internet.
QUIC in 30 seconds
- QUIC uses UDP mainly as a transport mechanism that can be deployed on top of the existing Internet, not simply because UDP is “faster.”
- It implements streams, flow control, loss recovery, and congestion control outside the kernel’s TCP implementation.
- It integrates TLS 1.3 directly into the protocol.
- This makes it possible to update much of the transport layer together with applications, without waiting for new operating system versions.
- The cost is moving into userspace work that TCP has had decades of optimization for in kernels and network cards.
QUIC’s own specification makes this motivation fairly clear. RFC 9000 defines QUIC as a secure, connection-oriented transport protocol whose packets are carried inside UDP datagrams specifically to make it easier to deploy on existing systems and networks.
The choice is especially interesting for a systems administrator because it changes a divide that seemed practically immovable for decades.
Simplifying things, the traditional HTTP-over-TCP stack can be represented like this:
Application
|
HTTP
|
TLS
-------------------- userspace
|
TCP
|
IP
-------------------- kernel
|
NIC
With HTTP/3 and QUIC, that layout changes:
Application
|
HTTP/3
|
QUIC
|-- Streams
|-- Reliability
|-- Flow Control
|-- Loss Recovery
|-- Congestion Control
|-- TLS 1.3
-------------------- userspace
|
UDP
|
IP
-------------------- kernel
|
NIC
This doesn’t mean QUIC “avoids the kernel.” UDP datagrams still pass through the operating system’s network stack. The difference lies in which layer is responsible for a significant part of the transport logic.
TCP Works Too Well to Change Easily
TCP has been one of the Internet’s basic building blocks for decades.
It provides reliable delivery, flow control, loss recovery, byte ordering, and congestion control. On top of that, Linux, other operating systems, and network card manufacturers have spent years improving its performance.
The problem arises when someone tries to change its behavior.
An application typically uses the TCP implementation provided by the operating system. Introducing certain transport improvements can require modifying the kernel, deploying that kernel version, and waiting for the change to reach enough clients and servers.
But there’s a second obstacle outside the endpoints.
Between a browser and a server, you may find:
Client
|
NAT
|
Firewall
|
Load Balancer
|
Proxy
|
Server
For decades, these devices have learned to work with TCP. Some inspect states, flags, or sequence numbers. Others maintain connection tables or perform various forms of traffic inspection and manipulation.
The result is what protocol engineering calls protocol ossification.
A feature can be perfectly valid under a new specification and still run into middleboxes that don’t understand the new behavior.
TCP’s success thus made its own evolution harder.
QUIC takes a different approach: instead of creating a new protocol directly on top of IP that every router, NAT, firewall, and operating system would have to learn, it uses UDP as its substrate.
UDP was already deployed.
Operating systems already had UDP sockets. Routers could carry it. NATs knew how to maintain state for it, and firewalls could allow it through.
QUIC could then layer the logic it needed on top.
RFC 9000 confirms this decision, noting that QUIC packets are carried in UDP datagrams precisely to make deployment on existing systems and networks easier.
Moving Transport to Userspace Changes the Pace of Evolution
The most interesting consequence shows up at the endpoints of the connection.
A significant part of QUIC’s behavior can live inside a library used by the browser, web server, CDN, proxy, or application.
This lets an organization modify its implementation and then ship a new version of the software.
It doesn’t necessarily have to wait for the feature to be added to the kernel and then for millions of systems to update that kernel.
The model can be simplified like this:
New QUIC implementation
|
v
Application/library update
|
v
New transport behavior
That makes it possible to work on congestion control algorithms, loss recovery, packet scheduling, and other transport components on deployment cycles that are different from the operating system’s.
Cloudflare offers a practical example with quiche, its open source implementation of QUIC and HTTP/3, part of a broader push to modernize its protocol stack alongside technologies like MASQUE, another HTTP/3-based protocol.
In May 2026, the company documented a particularly interesting problem related to CUBIC, used as quiche‘s default congestion controller.
An interaction between an optimization related to idle periods and the behavior of the congestion window could cause the window to get stuck at its minimum size after a congestion collapse.
Cloudflare described it as a kind of QUIC “death spiral.”
Beyond the specific bug, the case illustrates one of the operational advantages of this architecture: Cloudflare could inspect, modify, and roll out changes to transport behavior within its own QUIC implementation.
In TCP, comparable changes made inside the kernel implementation depend on a completely different development and deployment cycle.
That doesn’t mean QUIC is completely independent of the operating system, either. It still uses UDP, IP, sockets, buffers, and numerous kernel capabilities. The boundary has simply shifted.
QUIC’s Price Tag: TCP Has Had Decades of Optimization
Moving more work into userspace has consequences.
TCP isn’t just a mature protocol. It’s a protocol for which an enormous amount of acceleration infrastructure has been built.
Among other technologies, these include:
- TCP Segmentation Offload (TSO).
- Generic Segmentation Offload (GSO).
- Generic Receive Offload (GRO).
- Checksum offload.
- Socket stack optimizations.
- Dedicated NIC support.
- Zero-copy techniques in certain scenarios.
QUIC also needs to encrypt virtually all of its traffic and manage packets, ACKs, streams, losses, retransmissions, and congestion control within its own implementation.
At high speeds, the cost of processing huge numbers of datagrams individually can become a CPU problem.
Cloudflare studied this behavior as far back as its earliest QUIC deployments.
In a naive implementation, sending each UDP packet through an individual call to sendmsg() means one userspace-to-kernel transition per packet.
With millions of packets, those calls carry a noticeable cost.
One initial option is to use sendmmsg() to batch multiple messages into a single syscall.
Linux also provides an even more interesting tool: UDP Generic Segmentation Offload (UDP GSO).
Instead of handing each datagram to the kernel individually:
userspace
send()
packet 1
send()
packet 2
send()
packet 3
send()
packet 4
the application can provide one larger buffer:
userspace
super-buffer
|
v
kernel
|
v
segmentation
/ /
P1 P2 P3 P4
|
NIC
Linux added UDP GSO support through UDP_SEGMENT. The application can provide one large buffer and ask the kernel to split it into smaller datagrams afterward.
Cloudflare found that this technique significantly reduced the number of syscalls and improved the throughput of its experimental implementation.
This helps recover some of the efficiency TCP achieved over decades through kernel and hardware optimizations.
But it introduces another interesting problem: packet pacing.
A transport protocol shouldn’t simply send packets as fast as the CPU allows. Congestion control needs to space them out properly to avoid bursts that end up increasing loss and congestion.
Batching many packets together helps reduce syscalls, but it can conflict with the need to determine exactly when each one should be transmitted.
Linux offers mechanisms such as SO_TXTIME and SCM_TXTIME that let some of this timing scheduling move back into the kernel. It’s a good example of how the boundary between userspace and kernel keeps evolving, even when QUIC keeps the main transport logic outside of TCP.
QUIC Isn’t Simply “TCP Over UDP” Either
Another common oversimplification is to think of QUIC as a reimplementation of TCP inside UDP.
It shares responsibilities with TCP, but its design lets it solve some problems differently.
One of those is multiplexing.
HTTP/2 allows multiple streams within a single TCP connection. However, TCP provides only a single ordered byte stream.
If a TCP segment needed to reconstruct that stream is lost, later data has to wait even if it belongs to a different HTTP/2 stream — the same kind of head-of-line blocking behind vulnerabilities like MadeYouReset in HTTP/2.
QUIC builds streams into the transport layer itself. Losing data belonging to one stream doesn’t block the progress of other independent streams in the same way.
It also integrates TLS 1.3 into connection establishment.
RFC 9000 defines a handshake that combines the negotiation of cryptographic and transport parameters. QUIC can also use 0-RTT for certain previously established connections, although this mode comes with specific security considerations, including replay risk.
Another important change is Connection IDs.
A TCP connection is tightly tied to IP addresses and ports. QUIC includes connection identifiers that let it maintain state even when the network path changes.
This is especially useful on mobile devices.
A phone can start a connection over Wi-Fi and later switch to a cellular network. QUIC has connection migration mechanisms designed precisely to handle path changes or modifications caused by NAT.
QUIC Also Encrypts to Avoid a New Generation of Ossification
QUIC learned another lesson from TCP: the more middleboxes can observe about a protocol’s internal workings, the more likely they are to end up depending on those details.
That’s why a substantial portion of QUIC’s information is cryptographically protected.
A middlebox still sees IP, UDP, and certain information necessary to carry the datagrams, but it has much less visibility into the connection’s internal state.
This comes at a cost.
Diagnosing QUIC traffic from the network can be more complicated than observing certain TCP characteristics. Traditional monitoring and inspection tools don’t have access to the same information.
But that opacity is partly intentional.
If a firewall can’t build its behavior around internal details that should belong exclusively to the endpoints, it becomes harder for a future generation of QUIC to get stuck because millions of devices accidentally learned to depend on its current behavior.
The comparison between TCP and QUIC ends up being far removed from “slow TCP versus fast UDP”:
| Feature | TCP | QUIC |
|---|---|---|
| Transport | Implemented mainly in the kernel | Largely implemented in userspace |
| Underlying protocol | IP | UDP/IP |
| Reliability | TCP | QUIC |
| Congestion control | TCP/kernel | QUIC implementation |
| Native streams | No | Yes |
| Security | Usually TLS over TCP | Integrated TLS 1.3 |
| Evolution | Largely tied to the OS stack | Can evolve with the application/library |
| Middleboxes | Broad historical visibility | Less visibility |
| Offloads | Decades of optimization | More recent ecosystem |
| Connection migration | Not native to classic TCP | Built into QUIC |
This difference better explains why HTTP/3 uses QUIC.
Speed matters, especially on connections with latency, packet loss, or network changes. But the deeper architectural decision was to regain the ability to evolve the transport layer without having to modify TCP across the entire Internet.
TCP ended up in a paradoxical situation: its enormous success led operating systems, applications, and network devices to build decades of assumptions around its behavior.
QUIC uses UDP as a sufficiently simple, already-deployed layer on which to build a modern transport at the endpoints.
In exchange, it has to take on work that TCP gets essentially solved by kernels and NICs after decades of engineering.
That’s why projects like UDP GSO matter. QUIC gained freedom to evolve, but now it needs to build part of the performance machinery that TCP already had.
That trade-off between flexibility in userspace and accumulated efficiency in the kernel is far more interesting than simply saying HTTP/3 chose UDP because it was faster.
Frequently Asked Questions
Why does QUIC use UDP instead of TCP?
UDP provides a widely deployed foundation on which QUIC can implement its own transport logic without depending on changes to TCP in operating systems and middleboxes. RFC 9000 explicitly states that QUIC uses UDP datagrams to make deployment on existing systems and networks easier.
Is QUIC faster because UDP is faster?
Not necessarily. QUIC has to provide functions such as reliability, loss recovery, congestion control, and encryption on its own. Its performance advantages come from its overall design, not simply from using UDP.
Does QUIC run entirely in userspace?
No. Many implementations place most of the QUIC logic in userspace, but UDP datagrams still pass through the kernel’s IP/UDP stack and use the network interface. In addition, mechanisms like UDP GSO allow certain operations to be offloaded back to the kernel or the hardware.
What is the relationship between HTTP/3 and QUIC?
HTTP/3 uses QUIC as its transport protocol. QUIC provides secure connections, multiplexed streams, flow control, loss recovery, and congestion control, while HTTP/3 defines how the HTTP protocol works on top of that foundation.
Sources:
- IETF, RFC 9000, QUIC: A UDP-Based Multiplexed and Secure Transport.
- Cloudflare, Accelerating UDP Packet Transmission for QUIC, analysis of
sendmsg(),sendmmsg(), and UDP GSO. - Cloudflare, When “idle” isn’t idle: how a Linux kernel optimization became a QUIC bug, May 12, 2026.
- First Finger, Why QUIC Moved Transport Logic from the Kernel into Userspace, source article used as a starting point.

