Skip to main content

Command Palette

Search for a command to run...

Computer Networks #16: QUIC and HTTP/3

Updated
14 min readView as Markdown
Computer Networks #16: QUIC and HTTP/3
B
I am a third-year undergrad student at Chandigarh University. I am into building backend and applied AI stuff using the modern technologies. I loves to document my learning through the blogs so that I can contribute to the community.

What is QUIC?

At first this might sound strange because QUIC runs on top of UDP, and UDP itself does not provide reliability, ordered delivery, congestion control, or retransmission.

That does not mean QUIC behaves like raw UDP.

QUIC builds those features itself.

It provides reliable delivery, congestion control, flow control, multiplexed streams, encryption, loss recovery, connection establishment, and connection migration.

So the stack looks something like this:

Application
     ↓
    QUIC
     ↓
    UDP
     ↓
     IP

UDP mainly provides QUIC with a way to send datagrams across the existing Internet.

QUIC then implements most of the transport behavior that applications traditionally depended on TCP for.

QUIC Has TLS Built Into Its Handshake

With HTTPS over TCP, we normally have two different mechanisms involved.

TCP first establishes the transport connection.

Then TLS establishes the secure connection.

HTTP
 ↓
TLS
 ↓
TCP

QUIC handles this differently.

QUIC version 1 integrates TLS 1.3 directly into the QUIC handshake.

TLS handshake information is carried inside QUIC CRYPTO frames, and the keys generated by TLS are used directly to protect QUIC packets.

Because transport establishment and cryptographic negotiation happen together, QUIC can establish secure connections with fewer round trips.

Why Does QUIC Run Over UDP?

Technically, an IP protocol number could be assigned to a new transport.

The problem is deployment.

The Internet contains huge amounts of existing infrastructure such as routers, NAT devices, firewalls, load balancers, operating systems, and other middleboxes that already understand TCP and UDP.

Deploying a completely new transport directly over IP could require changes across large parts of this infrastructure, and unknown traffic may be blocked by some networks.

QUIC instead uses UDP as its substrate.

IP
 ↓
UDP
 ↓
QUIC

In IPv4, the IP Protocol field identifies UDP using protocol number 17. IPv6 has an equivalent Next Header mechanism.

From the network's perspective, QUIC traffic therefore looks like UDP traffic.

This gives QUIC two important advantages.

  • It can traverse much of the existing Internet infrastructure without requiring every router, NAT, and firewall to understand QUIC.

  • It can also be implemented in user space, which means protocol improvements can often be shipped through application or library updates rather than waiting for operating-system kernels to implement a new transport protocol.

User-space implementation is not a fundamental requirement of QUIC, but UDP makes it practical.

TCP + TLS vs QUIC Connection Establishment

Connection setup is one of the major advantages of QUIC.

Consider a fresh HTTPS connection using TCP and TLS 1.3.

First TCP needs its three-way handshake:

Client                         Server

SYN -------------------------->

    <---------------------- SYN + ACK

ACK -------------------------->

That costs approximately 1 RTT.

Then TLS performs its cryptographic handshake.

Client                         Server

ClientHello ------------------>

      <---------------- ServerHello
                       Certificate
                       Finished

Finished --------------------->

TLS 1.3 takes another round trip before the client can send normal protected application data.

So a fresh TCP + TLS 1.3 connection generally needs roughly:

TCP handshake      = 1 RTT
TLS 1.3 handshake  = 1 RTT

Total              ≈ 2 RTT

Older TLS versions could require even more.

QUIC combines transport establishment and the TLS handshake.

The client's first QUIC Initial packet can already contain the TLS ClientHello.

Conceptually:

Client                         Server

QUIC Initial
+ ClientHello ---------------->

                    QUIC Initial
                    + Handshake
                    <------------

Protected application data --->

A fresh QUIC connection can therefore make application data available after a 1-RTT handshake rather than first establishing TCP and then performing TLS separately.

QUIC and 0-RTT

QUIC can go one step further.

If the client has previously connected to the same server, it may be able to use 0-RTT connection resumption.

The server can previously provide information such as a TLS session ticket. The client remembers the required TLS and QUIC connection parameters.

On a later connection, the client can send application data immediately:

Client                         Server

Initial
ClientHello
0-RTT Application Data ------->

The client does not have to wait for a response from the server before transmitting that early application data.

This is what 0-RTT means.

It does not mean the server magically responds in zero time. It means the client can send application data without first waiting for a network round trip.

There is an important security trade-off.

0-RTT data can potentially be replayed by an attacker.

Imagine something like:

POST /transfer-money

If such a request were replayed, the operation could accidentally happen twice.

HTTP clients therefore have to be careful about which requests they send using early data. Without additional knowledge about the application, HTTP specifications allow clients to use safe methods for early data and prohibit unsafe methods.

Requests such as GET and HEAD are common examples of requests suitable for this model. It is not a QUIC-level rule saying that POST or DELETE can never use 0-RTT. The decision depends on HTTP semantics, replay safety, client behavior, and server configuration.

A server can also reject an early request using:

425 Too Early

and require the client to retry after the handshake completes.

Another Problem With TCP: Connection Identity

A traditional TCP connection is identified using information that includes:

Source IP
Source Port
Destination IP
Destination Port

This is commonly called the connection's 4-tuple.

Suppose you are using your phone on Wi-Fi.

Phone
  ↓
Wi-Fi
  ↓
Server

Then you leave the house and the phone switches to mobile data.

Your network address changes.

Phone
  ↓
5G
  ↓
Server

For a normal TCP connection, the existing connection usually cannot simply continue using this new network path.

A new TCP connection needs to be established.

QUIC was designed to handle this situation better.

QUIC Connection Migration

QUIC introduces Connection IDs.

Instead of identifying a connection only using the IP address and port combination, QUIC packets can contain a Connection ID that allows the endpoint to associate packets with the existing QUIC connection.

So even if the client's network changes:

Wi-Fi
192.x.x.x
    |
    | Connection ID = X7A92
    ↓
 Server

and later:

5G
10.x.x.x
    |
    | Connection ID = X7A92 / another valid CID
    ↓
 Server

the server can still associate the traffic with the same logical connection.

QUIC also performs path validation before fully trusting a new network path.

This allows QUIC connections to survive situations such as switching from Wi-Fi to mobile data and NAT rebinding without necessarily establishing a completely new connection.

QUIC can even issue multiple Connection IDs over a connection so that endpoints can change IDs during migration and reduce the ability of observers to correlate traffic across networks.

Now We Can Understand HTTP/3

HTTP/3 is essentially the mapping of HTTP semantics onto QUIC.

The familiar HTTP concepts remain:

GET
POST
PUT
DELETE

Headers
Status codes
Request body
Response body

HTTP/3 does not completely reinvent HTTP.

What changes significantly is the transport underneath it.

HTTP/1.1
   ↓
  TCP


HTTP/2
   ↓
  TCP


HTTP/3
   ↓
  QUIC
   ↓
  UDP

HTTP/3 gets several important features from QUIC, including stream multiplexing, per-stream flow control, low-latency connection establishment, encryption, and connection migration.

Why HTTP/2 Still Had Head-of-Line Blocking

HTTP/2 introduced one of the most important improvements over HTTP/1.1:

multiplexing.

Multiple requests could share a single connection.

Conceptually:

TCP Connection

Stream 1 → HTML
Stream 3 → CSS
Stream 5 → JavaScript
Stream 7 → Image

At the HTTP layer, these appear to be independent streams.

But TCP does not know anything about HTTP/2 streams.

TCP only sees one ordered byte stream:

Byte 1
Byte 2
Byte 3
Byte 4
Byte 5
...

Suppose one TCP segment containing some earlier bytes is lost.

TCP guarantees ordered delivery, so later bytes cannot be delivered to the application until those missing bytes have been retransmitted.

Even if the later bytes logically belonged to completely different HTTP/2 streams, HTTP/2 cannot access them yet.

TCP byte stream

A B C [LOST] E F G H
       ↑

Everything after this point waits.

This is called TCP-level head-of-line blocking.

HTTP/2 solved many forms of application-level blocking, but it could not remove this property of TCP.

QUIC Streams Solve This Differently

QUIC has streams directly inside the transport protocol.

Each stream has its own independent ordered byte sequence.

For example:

QUIC Connection

Stream 0 → Request A
Stream 4 → Request B
Stream 8 → Request C
Stream 12 → Request D

Suppose data belonging to Stream 4 is lost.

Stream 0  → continues
Stream 4  → waits for missing data
Stream 8  → continues
Stream 12 → continues

QUIC only needs to restore ordering inside the affected stream.

Other independent streams can continue making progress.

This is the major reason HTTP/3 eliminates TCP's connection-wide head-of-line blocking problem.

There is one nuance.

A single QUIC packet can contain frames belonging to multiple streams. If that packet is lost, every stream whose data was inside that packet might temporarily need retransmission.

Also, congestion control still operates at the connection level, so packet loss can reduce the sending rate of the whole connection.

So QUIC does not make packet loss irrelevant. It prevents one missing byte sequence from unnecessarily stopping unrelated streams.

One HTTP Request Gets One QUIC Stream

HTTP/3 maps each request-response exchange onto a client-initiated bidirectional QUIC stream.

Conceptually:

QUIC Connection

Stream 0
GET /index.html
← HTML response


Stream 4
GET /style.css
← CSS response


Stream 8
GET /app.js
← JavaScript response

These are not separate network connections.

They are independent logical streams multiplexed over the same QUIC connection.

Calling them "virtual connections" can help build intuition, but technically they are streams inside one QUIC connection.

The distinction matters because connection-level state such as congestion control and security is still shared.

HTTP/3 Frames vs QUIC Frames

This distinction is easy to miss because both HTTP/3 and QUIC use something called frames.

They are not the same thing.

HTTP/3 has application-layer frames such as:

HEADERS
DATA
SETTINGS
GOAWAY

QUIC has transport-layer frames such as:

STREAM
ACK
CRYPTO
MAX_DATA
MAX_STREAM_DATA
PING
CONNECTION_CLOSE

So when HTTP/3 sends a request, the structure can conceptually look like:

IP Packet
   ↓
UDP Datagram
   ↓
QUIC Packet
   ↓
QUIC STREAM Frame
   ↓
HTTP/3 HEADERS / DATA Frames
   ↓
HTTP Request

This layering is important.

For example, an HTTP/3 HEADERS frame may be carried as bytes inside a QUIC stream, while QUIC represents those bytes using one or more STREAM frames.

HTTP/3 frames can even span multiple QUIC packets.

Important QUIC Frames

A few QUIC frame types are particularly useful for understanding how the protocol works.

A STREAM frame carries application data belonging to a particular QUIC stream.

It contains information such as the stream ID, stream offset, data, and potentially a FIN bit.

A CRYPTO frame carries cryptographic handshake data, including the TLS handshake.

An ACK frame tells the peer which QUIC packets were successfully received.

Flow-control frames such as MAX_DATA and MAX_STREAM_DATA tell the sender how much additional data it is allowed to transmit.

Conceptually:

QUIC Packet

+--------------------------+
| QUIC Header              |
+--------------------------+
| ACK Frame                |
+--------------------------+
| STREAM Frame: Stream 4   |
+--------------------------+
| STREAM Frame: Stream 8   |
+--------------------------+

Multiple frame types can therefore be carried inside the same QUIC packet.

What Does FIN Mean in QUIC?

QUIC STREAM frames can contain a FIN bit.

The purpose of FIN is to indicate that the sender has reached the end of the data it intends to send on that stream.

For example:

STREAM ID = 8
OFFSET = 5000
DATA = ...
FIN = 1

This tells the receiver that this data reaches the final size of that stream.

The FIN bit itself is not what solves head-of-line blocking.

Head-of-line blocking is reduced because QUIC maintains ordering independently for each stream using stream IDs and offsets.

FIN simply marks completion of one direction of a stream.

This is similar in spirit to closing one side of a logical byte stream.

QUIC Packets and UDP Datagrams

TCP sends segments.

QUIC exchanges QUIC packets, which are carried inside UDP datagrams.

Conceptually:

IP Packet
   ↓
UDP Datagram
   ↓
QUIC Packet
   ↓
QUIC Frames

A UDP datagram can sometimes contain multiple QUIC packets, particularly during connection establishment.

Another interesting property is that when QUIC detects loss, it does not simply resend an identical packet with the same packet number.

The lost information is placed into new frames and transmitted inside a new QUIC packet with a new packet number.

This makes QUIC's loss-recovery model different from simply thinking of it as "TCP implemented over UDP."

Why HTTP/3 Uses QPACK Instead of HPACK

HTTP requests repeatedly contain similar headers:

Content-Type
Accept-Encoding
User-Agent
Cookie
Authorization

Sending these strings repeatedly wastes bandwidth.

HTTP/2 solves this using a header compression mechanism called HPACK.

HTTP/3 instead uses QPACK.

Why introduce another compression format?

Because HTTP/2 runs over one ordered TCP byte stream.

QUIC streams, on the other hand, are independent and can make progress at different speeds.

A header compression mechanism designed around globally ordered delivery can accidentally recreate dependencies between streams.

QPACK was designed specifically for QUIC's stream model.

It uses dedicated streams for managing the dynamic compression table and gives implementations control over how much blocking they are willing to allow.

So:

HTTP/2 → HPACK
HTTP/3 → QPACK

QPACK can still introduce limited blocking when a header block depends on dynamic-table entries that have not arrived yet, but its design prevents header compression from recreating the same kind of connection-wide dependency that HTTP/3 is trying to avoid.

QUIC Is Not Only for HTTP/3

One important thing to remember is:

QUIC and HTTP/3 are not the same protocol.

QUIC is a general-purpose transport protocol.

HTTP/3 is one application protocol built on top of QUIC.

The relationship is similar to:

HTTP
SMTP
SSH
PostgreSQL
   ↓
  TCP

QUIC can also support multiple application protocols:

HTTP/3
DNS over QUIC
Other protocols
      ↓
     QUIC
      ↓
     UDP

HTTP/3 just happens to be the most widely recognized application of QUIC.

What Happens if UDP Is Blocked?

Because QUIC uses UDP, there are networks where QUIC traffic may not work properly.

This is another reason replacing TCP across the whole Internet did not happen overnight.

HTTP clients can fall back to HTTP/2 or HTTP/1.1 over TLS and TCP when HTTP/3 cannot be used.

Conceptually:

Try:

HTTP/3
  ↓
QUIC
  ↓
UDP

If unavailable:

HTTP/2
  ↓
TLS
  ↓
TCP

This makes HTTP/3 deployable without requiring every network on the Internet to support QUIC perfectly from day one.

Putting Everything Together

The evolution becomes easier to understand when we look at the problem each version was trying to solve.

HTTP/1.1

Multiple TCP connections
Limited request concurrency
Application-level blocking
        ↓

HTTP/2

One TCP connection
Multiple HTTP streams
Binary framing
HPACK
        ↓

Problem remaining:

All streams still depend on
one ordered TCP byte stream
        ↓

TCP packet loss
can block unrelated streams
        ↓

HTTP/3

HTTP semantics
        ↓
QUIC streams
        ↓
Independent stream ordering
        ↓
UDP

QUIC also improves connection establishment:

TCP + TLS 1.3
≈ 2 RTT for a fresh secure connection

QUIC
≈ 1 RTT for a fresh connection

QUIC resumption
0-RTT application data can sometimes
be sent immediately

And because QUIC connections can use Connection IDs:

Wi-Fi → Mobile Data

TCP:
connection usually needs rebuilding

QUIC:
existing connection can potentially migrate
to the new network path

Final Mental Model

The easiest way to remember the relationship is:

HTTP/3
     ↓
Defines HTTP semantics and HTTP frames

QUIC
     ↓
Provides streams, reliability,
flow control, congestion control,
TLS 1.3 security and connection migration

UDP
     ↓
Provides datagrams and deployability

IP
     ↓
Routes packets across networks

HTTP/3 is not faster simply because it uses UDP.

Raw UDP gives none of the reliability guarantees that HTTP needs.

The real improvement comes from QUIC building a modern transport model on top of UDP: secure connection establishment, independent streams, loss recovery, flow control, congestion control, and connection migration.

HTTP/2 showed that multiplexing HTTP requests was valuable.

HTTP/3 goes one layer deeper and makes multiplexing a property of the transport protocol itself.

That is the fundamental idea behind QUIC and HTTP/3.