HTTP/3 and QUIC are built on UDP, meaning you can’t just tcpdump and expect to see the familiar handshake.

Let’s see what’s actually happening when your browser fetches a webpage over HTTP/3. We’ll simulate a client connecting to a simple HTTP/3 server.

# On the server side, start a Python HTTP/3 server
# Ensure you have a recent version of aioquic installed: pip install aioquic
# You'll need a self-signed certificate and key.
# openssl req -x509 -newkey rsa:4096 -keyout cert.key -out cert.pem -days 365 -nodes
python -m aioquic.examples.http3_server --host 127.0.0.1 --port 4433 --certificate cert.pem --private-key cert.key

# On the client side, use curl to fetch a page
# Ensure you have a recent version of curl compiled with HTTP/3 support.
curl --http3 https://127.0.0.1:4433/

You’ll see curl output the HTML content. Now, let’s dive into the packets.

The core of HTTP/3 is QUIC, which runs over UDP. This means the traditional TCP handshake is gone. Instead, QUIC establishes a connection with a "0-RTT" or "1-RTT" handshake.

  • 0-RTT: The client sends an initial UDP packet containing connection establishment information and its first HTTP/3 request. If the server is ready, it can respond immediately with the requested data.
  • 1-RTT: If 0-RTT isn’t possible (e.g., first connection, server not ready), the client sends a connection establishment packet. The server responds, and then the client sends its HTTP/3 request.

This is why traditional TCP analysis tools fall short. You need tools that understand the QUIC packet structure.

Here’s where qvis comes in. It’s a command-line tool that visualizes QUIC connection events. You can pipe tcpdump output to it.

First, capture the UDP traffic. On the server or client machine:

sudo tcpdump -i lo -n udp port 4433 -w http3_traffic.pcap

Now, run qvis on the captured file:

qvis http3_traffic.pcap

qvis will show you a timeline of QUIC events: connection creation, packet loss, retransmissions, and application data flow. You’ll see distinct phases corresponding to the QUIC handshake and the subsequent HTTP/3 requests. Notice the UDP packets and how they contain encrypted QUIC frames, not plain HTTP.

Wireshark, with its built-in QUIC dissector, is your next best friend. Open the http3_traffic.pcap file in Wireshark.

In the packet list pane, you’ll see UDP packets. If Wireshark correctly identifies the QUIC protocol, it will label them as such. You can filter by quic.

Look for the initial Initial QUIC packet from the client. This is the start of the connection establishment. You’ll then see Handshake packets, and finally 1-RTT packets carrying the actual HTTP/3 frames.

The most surprising thing about HTTP/3’s performance benefits is that they aren’t solely due to header compression (like HPACK in HTTP/2) or multiplexing. The real magic is in connection migration and mitigating head-of-line blocking at the transport layer. Because QUIC is built on UDP, each stream within a QUIC connection is independently managed. If one stream experiences packet loss, it doesn’t block other streams on the same connection. This is a fundamental shift from TCP, where a single lost packet can stall all multiplexed HTTP/2 streams.

When debugging, pay close attention to the quic.stream_id field in Wireshark. Each HTTP/3 request/response pair is assigned a unique stream ID within the QUIC connection. You can follow a specific stream to isolate issues related to a particular request. Also, look for QUIC retransmission in Wireshark’s packet details or packet loss and retransmission in qvis output.

The next thing you’ll likely grapple with is understanding the nuances of QUIC’s flow control mechanisms, particularly the difference between connection-level and stream-level flow control windows, and how they can lead to perceived stalls if not managed correctly by the application.

Want structured learning?

Take the full Http3 course →