HTTP/3 is the latest iteration of the Hypertext Transfer Protocol, and its secret sauce is QUIC, a new transport layer protocol that swaps out TCP for a more efficient, modern handshake.

Let’s see QUIC in action. Imagine a web server running Nginx with the quic module enabled. A client, like Chrome, initiates a connection.

First, the client sends a QUIC Initial packet. This packet contains its initial transport parameters and potentially TLS handshake information. The server, if configured for QUIC, receives this.

# Example Nginx configuration snippet for QUIC
server {
    listen 443 quic reuseport;
    listen 443 ssl http2; # Fallback to TCP/HTTP2
    # ... other ssl and http2 configurations
    ssl_protocols TLSv1.3;
    ssl_certificate /etc/nginx/ssl/cert.pem;
    ssl_certificate_key /etc/nginx/ssl/key.pem;
}

The server responds with its own Initial packet, also carrying QUIC transport parameters and TLS handshake data. This exchange, unlike TCP’s multi-round trip handshake (SYN, SYN-ACK, ACK), consolidates the transport and TLS handshakes into a single round trip, often zero round trip (0-RTT) for subsequent connections.

Client -> Server: Initial QUIC packet (ClientHello, Transport Params)
Server -> Client: Initial QUIC packet (ServerHello, Transport Params, Finished)

This initial exchange establishes the connection, negotiates TLS 1.3, and sets up UDP datagrams for reliable, ordered delivery. The "under the hood" magic is QUIC’s multiplexing. With TCP, if one stream of data (say, an image) is delayed, all other streams on that connection (like CSS or JavaScript) also stall – this is "head-of-line blocking." QUIC, running over UDP, implements its own stream multiplexing. Each logical HTTP/3 stream is a separate QUIC stream. If a UDP packet carrying data for one stream is lost, only that specific stream is affected; others continue to make progress.

Consider a scenario where a user requests a webpage with many small assets. With TCP, each asset might require its own connection or, if multiplexed over HTTP/2, could suffer from head-of-line blocking if one asset’s packets are delayed. With HTTP/3 and QUIC, each asset can be its own QUIC stream. If one image’s packets get lost, the CSS and JavaScript for the page can still be delivered without waiting.

QUIC Connection ID: XXXXXXXX
  -> QUIC Stream 1 (CSS): Data Packet A
  -> QUIC Stream 2 (Image): Data Packet B (Lost)
  -> QUIC Stream 3 (JS): Data Packet C

When packet B is lost, QUIC’s congestion control and reliability mechanisms kick in only for stream 2. Streams 1 and 3 continue to be processed. The client’s QUIC implementation will detect the missing packet for stream 2 and request a retransmission, but it won’t hold up the rendering of the page based on the CSS and JS it has already received.

The internal mechanics of QUIC’s congestion control are also a significant departure. Instead of relying on TCP’s built-in congestion algorithms, QUIC implements its own, allowing for more flexible and potentially faster evolution of these algorithms. This includes features like improved loss detection and recovery.

The most surprising true thing about QUIC is that it’s not a replacement for TCP in the sense of being a new layer below the application. Instead, it’s a new protocol that runs over UDP. This means that QUIC implements all the reliability, ordering, and congestion control that TCP provides, but it does so in user-space, allowing for faster iteration and deployment of new features and optimizations than is possible with kernel-level TCP implementations.

This UDP-based approach also means QUIC can easily traverse firewalls and NATs that might block or interfere with custom TCP options, as UDP is generally more permissive.

When you connect to a server using HTTP/3, your browser might initially attempt a QUIC connection. If the server supports it, you’ll get the benefits of faster handshakes and improved multiplexing. If not, it falls back gracefully to HTTP/2 over TCP.

The next concept you’ll run into is how QUIC handles connection migration, allowing a client to seamlessly switch IP addresses or ports without dropping the connection.

Want structured learning?

Take the full Http course →