Prerequisites: the transport overview. Outcome: explain the difference between packets and frames and calculate an Initial header’s size. Allow: 30 minutes. This lesson is optional on your first pass through level 101.

Packet structure and the rules of engagement

This lesson answers a specific question: what does a QUIC packet actually look like, and what rules govern the handshake before either side can talk about anything else?

Two kinds of packet header

Every QUIC packet has either a long header or a short header (RFC 9000 §17). Which one is used depends on what stage of the connection you are in. Long header packets carry the handshake itself, before the connection is fully set up. There are four types, distinguished by a 2-bit type field in the first byte (RFC 9000 §17.2): Short header packets carry everything after the handshake completes — ordinary application data, once both sides have negotiated encryption keys. This is called a 1-RTT packet, and it is what carries the actual video frames, chat messages, and robot commands your application cares about.

The Initial packet, field by field

Figure (a) below is RFC 9000’s own Initial packet format (§17.2.2, Figure 15), redrawn with every field’s bit or byte width labeled — the same way any networking textbook draws a header. Figure (b) is the 1-RTT (short header) packet next to it, for direct comparison (§17.3.1, Figure 19). QUIC Initial packet and 1-RTT packet header fields, bit by bit Reading figure (a) left to right: the first byte alone packs five fields — Header Form (1 bit, always 1 for a long header), Fixed Bit (1 bit, always 1), Long Packet Type (2 bits, 00 for Initial), 2 Reserved Bits, and Packet Number Length (2 bits, encoding how many of the following bytes are the packet number). Only after that one byte do you reach anything human-recognizable: a 4-byte Version, then a Destination Connection ID and Source Connection ID, each preceded by its own 1-byte length field (RFC 9000 caps each ID at 20 bytes in QUIC version 1). Two fields exist only on the Initial packet: Token Length and Token. A token can come from Retry or from a previous connection’s NEW_TOKEN frame. A Length field and the Packet Number itself come last, immediately before the encrypted payload — which, for an Initial packet, can contain CRYPTO, ACK, PING, PADDING, or transport CONNECTION_CLOSE frames (RFC 9000 §17.2.2 spells this out explicitly; anything else is a protocol violation). Figure (b), the 1-RTT packet, is dramatically smaller: one byte (Header Form now 0, a Spin Bit used only for latency measurement, Key Phase, and the same Packet Number Length encoding), a Destination Connection ID, a Packet Number, and then payload. No Version, no connection-ID lengths, no Token — every field that existed only to negotiate something is gone, because by the time a 1-RTT packet is sent, there is nothing left to negotiate.
Worked example. Suppose a client and server have each chosen an 8-byte connection ID (a common default), and the packet number fits in 2 bytes. An Initial packet’s header, before a single byte of the actual handshake message, costs: 1 (first byte) + 4 (Version) + 1 (DCID Len) + 8 (DCID) + 1 (SCID Len) + 8 (SCID) + 1 (Token Length, encoding zero) + 0 (Token) + ~2 (Length) + 2 (Packet Number) = 28 bytes of overhead. A 1-RTT packet with the same 8-byte connection ID and 2-byte packet number costs only 1 + 8 + 2 = 11 bytes. Each packet carries the header for its packet type. A handshake can require several packets and retransmissions. These counts exclude payload encryption overhead.
client and server exchanging Initial, Handshake, and 1-RTT packets over one round trip

Frames live inside packets

A packet is the thing that travels over the network. Inside a packet’s payload — the part after all the header fields in the figure above — are one or more frames: the actual instructions. RFC 9000 §19 defines about twenty frame types; you do not need to memorize all of them, but a handful explain a lot about how QUIC behaves: A single QUIC packet routinely carries several small frames bundled together — an ACK, a PING, and a slice of stream data can all ride in one packet. Bundling can reduce per-packet overhead. The handshake design, rather than frame bundling alone, determines connection-establishment round trips. Take the STREAM frame as a concrete example of what “a frame” actually looks like on the wire (RFC 9000 §19.8, Figure 32): STREAM frame fields: type byte with OFF/LEN/FIN flags, Stream ID, optional Offset, optional Length, Stream Data Notice that the frame’s own Type byte does double duty: its three low-order bits are not part of a number at all, but three independent flag bits — OFF (an Offset field follows), LEN (a Length field follows), and FIN (this frame ends the stream) — which is why RFC 9000 writes the type as a range, 0x08 to 0x0f, rather than one fixed value: it is really eight closely related frame types sharing one shape. A STREAM frame’s fixed overhead — Type plus Stream ID — can be as little as 2 bytes, which is why many small, frequent updates from many different streams can share the space inside one QUIC packet cheaply.

The handshake’s safety rules

QUIC’s handshake carries a few deliberate rules that exist specifically to stop QUIC from being used as a weapon against someone else. The 1200-byte floor. A client must expand each UDP datagram carrying an Initial packet to at least 1200 bytes (RFC 9000 §14.1), even if the real request inside it is much smaller. This exists because a QUIC server’s response to a new connection is often larger than the request that triggered it — and an attacker could otherwise send a tiny forged packet claiming to be from a victim’s IP address, and get the server to blast a much bigger response at that victim. This supplies a minimum datagram size and supports path validation. The separate anti-amplification limit bounds the server’s response before address validation. The three-times limit. Until a client has proven it owns the address it claims to be sending from, a server must not send that address more than three times as many bytes as it has received from it (RFC 9000 §8.1). This is called the anti-amplification limit, and it is the second half of the same defense: even with a padded request, a server caps its own blast radius until the address is validated. Retry packets. If a server is under enough load or suspicion that it wants proof before committing any state at all, it sends a Retry packet — the client must echo a server-chosen token back before the real handshake proceeds. This costs the client one extra round trip, but only when the server decides it is warranted. Stateless reset. Servers restart. When a server that lost all memory of a connection receives a packet for it anyway, it has no key to reply properly — but it can send back a stateless reset token, a 16-byte value derived from the connection ID (RFC 9000 §10.3), that unmistakably tells the client “I don’t know this connection, stop.” Without this, a restarted server would have to silently drop packets it can’t decrypt, leaving the client to time out slowly instead of failing fast. Idle timeout. Both sides state, during the handshake, the longest they are willing to hold a silent connection open. QUIC uses the smaller nonzero advertised timeout when either endpoint enables it (RFC 9000 §10.1). This is why a real-time SDK sends periodic keep-alives on a connection that is momentarily quiet — waiting out an idle timeout is the alternative, and it is slower. Version negotiation. If a server does not support the QUIC version a client proposed, it replies with a Version Negotiation packet listing the versions it does support (RFC 9000 §6), and the client retries with one of those. In practice, nearly everyone speaks QUIC version 1 today, so you will rarely see this in the wild — but the mechanism exists so the protocol can evolve without breaking older clients outright. None of these rules change what your application code looks like. They exist below your SDK, enforced by the QUIC library it’s built on. They are worth knowing anyway, because “why did this connection get retried” or “why did the server just reset that” almost always traces back to one of these six rules.

Check your understanding

Calculate the Initial header size with two 20-byte connection IDs and a four-byte packet number. Assume an empty token and a two-byte Length field.
The header contains 1 + 4 + 1 + 20 + 1 + 20 + 1 + 0 + 2 + 4 = 54 bytes. This excludes the encrypted payload and authentication tag. The two connection IDs add 24 bytes compared with the earlier example; the packet number adds two.
Completion check: distinguish an IP packet, a QUIC packet, and a QUIC STREAM frame. Next: congestion control, or return to the transport overview.