HTTP/3’s most surprising feature is that it doesn’t actually use TCP.

Let’s see how this looks in practice. Imagine a simple Nginx server configured for HTTP/3.

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    listen 443 http3 ssl;
    listen [::]:443 http3 ssl;

    server_name example.com;
    root /var/www/example.com;
    index index.html;

    ssl_certificate /etc/nginx/ssl/example.com.crt;
    ssl_certificate_key /etc/nginx/ssl/example.com.key;

    # HTTP/3 specific settings
    quic_retry on;
    quic_cc cubic;
    quic_max_idle_timeout 10s;
    quic_max_connection_count 10000;
    quic_max_stream_count 100;
    quic_max_stream_data_bidi_local 1MB;
    quic_max_stream_data_bidi_remote 1MB;
    quic_max_stream_data_unidi_local 512KB;
    quic_max_stream_data_unidi_remote 512KB;
    quic_max_data_bidi_local 3MB;
    quic_max_data_bidi_remote 3MB;
    quic_max_data_unidi_local 1MB;
    quic_max_data_unidi_remote 1MB;
}

HTTP/3 runs over QUIC, which itself runs over UDP. This is a fundamental shift. Instead of relying on TCP’s established handshake, congestion control, and reliability mechanisms, QUIC implements these features in user space, directly on top of UDP. This allows for faster connection establishment (0-RTT or 1-RTT handshakes) and avoids TCP’s "head-of-line blocking" problem, where a lost packet on one stream can stall all other streams multiplexed over the same TCP connection.

The core of HTTP/3’s performance comes from QUIC’s ability to manage multiple independent streams over a single connection without interdependencies. When a packet is lost, only the specific stream affected is paused, allowing other streams to continue unimpeded. This is a significant improvement over TCP’s behavior, especially in lossy network conditions.

You control the behavior of these QUIC connections through a set of parameters, typically configured within your web server. These parameters are crucial for tuning performance, security, and resource usage.

quic_retry on; enables the use of QUIC’s retry mechanism, which helps mitigate amplification attacks by requiring clients to prove they can receive UDP packets before a full connection is established.

quic_cc cubic; specifies the congestion control algorithm. cubic is a widely adopted algorithm that performs well in a variety of network conditions, adapting to bandwidth changes by using a cubic function to determine its congestion window. Other options might include reno or bbr depending on the QUIC implementation.

quic_max_idle_timeout 10s; sets the maximum duration a connection can remain idle before being closed by the server. This is important for freeing up server resources when clients are no longer actively using the connection.

quic_max_connection_count 10000; limits the total number of concurrent QUIC connections the server will accept. This is a critical protection against denial-of-service attacks and helps manage server load.

quic_max_stream_count 100; defines the maximum number of bidirectional streams that can be open concurrently on a single QUIC connection. HTTP/3 uses streams for multiplexing requests and responses.

The quic_max_stream_data_* parameters (e.g., quic_max_stream_data_bidi_local 1MB;) control the maximum amount of data that can be in flight for a single stream, on either the local or remote side. These limits prevent a single misbehaving or very active stream from consuming all available connection bandwidth.

Similarly, quic_max_data_* parameters (e.g., quic_max_data_bidi_local 3MB;) set the maximum amount of data that can be in flight for all streams combined on a connection, again separated by local/remote and bidirectional/unidirectional. These are the overall flow control limits for the QUIC connection.

A common point of confusion is how these stream and connection data limits interact. The connection-level limits are the aggregate of all stream-level limits. If you have many streams, each capped at 1MB of data, and the connection-level limit is 3MB, you can only have three such streams active simultaneously before hitting the connection limit, even if each stream individually could send more.

The next hurdle in understanding HTTP/3 is how its stateless reset mechanism works to protect against certain types of denial-of-service attacks and ensure graceful connection termination.

Want structured learning?

Take the full Http3 course →