11.2 HTTP/3, QPACK, Discovery and Deployment
HTTP/3 uses QUIC to reduce transport-level waits across requests, but deployment still has three questions to answer: how clients discover it, how they fall back when UDP fails, and how packets reach existing connection state after a network change. This lesson follows requests through compression, discovery, and routing, then uses curl's protocol output to distinguish a successful request from actual HTTP/3 use.
HTTP/3 preserves HTTP methods, status codes, fields, and cache semantics while mapping frames and multiplexing onto QUIC. Applications usually need no REST interface redesign, but transport and operational configuration change.
1. The layers of HTTP/1.1, HTTP/2, and HTTP/3
This diagram shows the transport relationships only. HTTP/1.1 and HTTP/2 also need TLS when used for HTTPS:
HTTP/1.1: semantics -> HTTP/1.1 textual framing -> TCP -> IP
HTTP/2: semantics -> HTTP/2 binary frames -> TCP -> IP
HTTP/3: semantics -> HTTP/3 frames -> QUIC -> UDP -> IPAn HTTP/1.1 persistent connection can handle several requests. Pipelining allows another request to be sent before the previous response arrives, although responses must remain ordered and client support is limited. One outstanding request per connection is a common implementation choice, not the protocol's full capability.
HTTP/2 interleaves responses on different streams, removing HTTP/1.1 pipelining's response-order constraint. A TCP byte gap still blocks delivery of subsequent data. HTTP/3 reduces that cross-stream blocking through independent QUIC streams.
Whether HTTP/3 is faster depends on the target users' networks and workloads. Low-loss, low-RTT paths, existing warm TCP/TLS connections, UDP throttling, and QUIC implementation CPU costs can all change the result. A protocol name cannot replace a measurement.
2. Request streams and critical unidirectional streams
Each ordinary request and its response use one client-initiated bidirectional QUIC stream. Messages travel in separate directions. This sequence diagram does not require the whole request body to finish before a response begins:
client -> server: HEADERS -> optional DATA -> optional trailers(HEADERS)
server -> client: optional 1xx HEADERS -> final HEADERS
-> optional DATA -> optional trailers(HEADERS)HTTP/3 frames and QUIC packets have different boundaries. An HTTP/3 frame may span several QUIC STREAM frames and packets; one QUIC packet can contain data from several streams. The receiver first recovers stream bytes, then parses HTTP/3 frames.
Connections also use these critical unidirectional streams:
- One control stream per endpoint, starting with
SETTINGS, then carrying control frames such asGOAWAYandMAX_PUSH_IDas permitted for that role. - A QPACK encoder stream carrying dynamic-table updates.
- A QPACK decoder stream carrying feedback such as field-section acknowledgments.
Each endpoint must allow its peer to create at least three unidirectional streams. Certain configurations, such as not using dynamic tables, permit omitting QPACK streams, but a created critical stream cannot be closed arbitrarily. Its closure is a connection error. RFC 9114 §6.2, RFC 9204 §4.2
For an unknown unidirectional stream type, a receiver must stop reading that stream or discard its subsequent data. The unknown type alone must not cause a connection error. Stream and buffer limits still apply so that extension compatibility does not permit unlimited resource consumption.
3. How QPACK controls compression dependencies
HTTP/2's HPACK uses TCP's globally ordered delivery to synchronize dynamic tables. QUIC streams arrive independently, so compressed request fields can arrive before the table entries they reference. HTTP/3 uses QPACK to handle this difference.
A field section is a group of header or trailer fields. Each direction has an encoder and decoder state pair:
- The encoder stream sends insertion, duplication, and capacity-update instructions to the peer decoder.
- The decoder stream returns field-section acknowledgments, stream cancellations, and insert-count increments to the peer encoder.
- A field section on a request stream carries Required Insert Count and Base, describing dynamic-table dependencies and the reference point for indexing.
Suppose the decoder has received 5 insertions, but a field section has Required Insert Count 6. Decoding must wait for the sixth insertion. A different field section using only the static table can proceed. That wait comes from a QPACK dependency, not from QUIC putting every stream back into one ordered queue.
The decoder advertises SETTINGS_QPACK_BLOCKED_STREAMS, limiting how many streams the peer encoder may put into a potentially blocked state. Its default is 0. The encoder uses acknowledged insertion progress when choosing references and can use static-table references or literals instead. Once the allowance is exhausted, it cannot create another blocked-stream dependency. RFC 9204 §2.1.2
A separate setting, SETTINGS_QPACK_MAX_TABLE_CAPACITY, limits dynamic-table capacity and also defaults to 0. Increasing capacity and the blocked-stream allowance may improve compression, at the cost of memory and possible waits. Each encoder's table must agree with the peer decoder's table; field sections simply need not arrive in global order.
4. Discover services through Alt-Svc or HTTPS DNS records
An origin can send this field in an HTTP/1.1 or HTTP/2 response to advertise HTTP/3 on port 443 of the same host, with a cache lifetime of 86400 seconds:
Alt-Svc: h3=":443"; ma=86400Alt-Svc belongs to HTTP. There is no “Alt-Svc DNS record” to query with dig. Clients may cache the alternative service information, but must still authenticate the original origin when connecting. A different service address changes neither the URL's origin nor the identity the certificate must cover. RFC 7838 §2.1
DNS SVCB/HTTPS resource records can advertise service bindings, ALPN values such as h3, ports, and address hints. With a version of dig that supports the HTTPS record type, inspect a response using this query. The example domain is not guaranteed to publish such a record:
dig example.com HTTPSInspect the Answer section for an HTTPS record and its alpn values. An empty answer does not disprove HTTP/3 support, since Alt-Svc provides another discovery route. Clients can try connections using ipv4hint / ipv6hint while still querying the target's A/AAAA records; available A/AAAA results should take precedence. RFC 9460 §7.3
DNSSEC authenticates DNS data, while DoH/DoT protects transport to the resolver. Neither replaces HTTPS origin authentication.
5. Count fallback in latency when UDP fails
Enterprise networks, firewalls, NATs, or rate limits may block or impair UDP/443. Unless an application explicitly requires QUIC alone, clients generally need an HTTP/2 or HTTP/1.1 path over TCP as well.
Waiting for a slow QUIC failure before starting TCP adds fallback latency. Racing connections, caching failed alternatives, and reusing existing connections can reduce the wait, while also causing additional connection attempts. Browser timeout values are implementation policy.
Record attempted protocols, failure reasons, fallback duration, and the final negotiated protocol together. An HTTP 200 proves the request succeeded; it does not prove UDP worked.
6. Load balancing must preserve connection ownership
NAT rebinding or migration can change a source IP address or UDP port. Selecting a backend solely by the five-tuple can send packets to a server without the connection's keys or stream state. Common approaches include:
- Terminating QUIC at an edge proxy or load balancer and opening a separate upstream connection.
- Decoding routing information from server-issued CIDs or maintaining CID-to-backend mappings.
- Coordinating connection state between backends, with substantial cost for keys, packet numbers, streams, and timers.
The client chooses the destination CID in its first Initial, so the load balancer also needs initial routing rules. Once connected, multiple server-issued CIDs must keep reaching the same state through rotation. Applying consistent hashing to arbitrary CIDs does not guarantee that property, and backend-set changes also need to preserve existing connection ownership.
The QUIC-LB draft specifies algorithms for generating routable CIDs. The version cited here, draft-ietf-quic-load-balancers-21, is an Internet-Draft. Deployments need to check the draft versions and encoding formats their implementations support. QUIC-LB draft
Deployment must still handle CID linkability, key rotation, backend draining, and CID issuance and retirement. A stateless reset lets an endpoint that has lost state signal connection termination; it does not recover the connection on another backend. Reset tokens also need protection.
7. Handle 0-RTT at the HTTP layer
A proxy forwarding a request before its client TLS handshake completes must include Early-Data: 1; it must not remove an existing field either. If the gateway cannot confirm upstream support for this field and correct 425 handling, it should wait for the client handshake before forwarding or return 425 itself.
A server receiving Early-Data: 1 must return 425 Too Early if it cannot process the request safely. Waiting for the current hop’s handshake cannot remove replay risk from early data on a previous hop. Clients that sent a request in early data should usually retry automatically, but the retry must not use early data again. This status addresses early requests; it is not a general retry signal for arbitrary business failures. RFC 8470 §5.2
Policy cannot rely on the method name alone:
- GET may consume a one-time link, trigger billing, or cause side effects that violate the method's semantics.
- PUT is idempotent in HTTP semantics, but repeated processing can still add auditing or notification costs.
- Credentials in early data may be replayed with the request in the same service context.
- Distributed replay-prevention caches require trade-offs in consistency, availability, and cross-instance coordination.
For important state changes, waiting for handshake completion is an easier default to reason about. If early data is needed, define idempotency keys, replay windows, credential scope, and duplicate-response semantics. An idempotency key alone does not prove that every side effect is deduplicated.
8. Verify behavior with protocol output and logs
These commands require curl with an HTTP/3 backend; --http3-only requires curl 7.88.0 or later. Replace example.com with a host you are authorized to test that has HTTP/3 configured. Packet capture requires the appropriate local permissions and should run in your own test environment:
# Check the version and look for HTTP3 in the Features line
curl --version
# Try HTTP/3 with fallback; print the version actually used
curl -v --http3 --max-time 15 -o /dev/null \
-w 'http_version=%{http_version}\n' https://example.com/
# Disable fallback to check the QUIC path separately
curl -v --http3-only --max-time 15 -o /dev/null \
-w 'http_version=%{http_version}\n' https://example.com/
# Observe UDP/443 during the test; QUIC can also use other ports
tcpdump -ni any -c 20 'udp port 443'http_version=3 means the transfer used HTTP/3. Another version with --http3 may simply mean fallback succeeded. If --http3-only fails, distinguish missing client support, DNS or certificate errors, server configuration, and UDP path failures. Record curl and backend-library versions with the detailed error.
These commands test connection and protocol selection. They are neither a performance benchmark nor a guarantee of 0-RTT. Consult the matching curl documentation for version-specific racing behavior. curl HTTP/3 documentation, curl --http3-only
QUIC application payloads are encrypted, and parts of the header are protected. A capture alone cannot expose HTTP fields. HTTPS over TCP also hides application content, so TCP must not be equated with plaintext HTTP. Combine captures with:
- Application access logs and request traces.
- QUIC metrics: handshake results, 0-RTT acceptance/rejection, PTO/loss, RTT, congestion window and pacing, flow-control blocking, migration, and path validation.
- qlog where supported by the implementation.
- TLS key logs and Wireshark in authorized environments, treating key logs as sensitive credentials.
- Optional passive signals such as the spin bit, without assuming every implementation or connection enables them.
9. Capacity and denial-of-service boundaries
QUIC/TLS handshakes consume server CPU and state. Check these deployment constraints together:
- Address-validation and Retry policies.
- Per-IP, per-prefix, and global handshake rate limits.
- The three-times byte limit when sending to unvalidated addresses.
- Limits on connections, streams, buffers, and QPACK table capacity.
- Protection for stateless-reset tokens and keys.
- Certificate-chain size and handshake packetization.
- UDP receive buffers, batching, GRO/GSO, and worker affinity.
- HTTP/2 or HTTP/1.1 fallback capacity over TCP.
After opening UDP/443, test NAT timeouts, CID routing, path MTU, rate limits, and fallback failures. One test is to block UDP in a test network and check that TCP fallback is timely, then restore UDP and confirm the final protocol returns to HTTP/3. Keep requests and timeout settings the same to compare the two results.
10. Check your understanding
- Why do HTTP/3 frames, QUIC STREAM frames, and QUIC packets have different boundaries?
- A decoder has received 5 QPACK insertions and gets a field section with Required Insert Count 6. What must it wait for?
- Which protocols provide Alt-Svc and HTTPS DNS records? Can an empty HTTPS answer rule out HTTP/3?
- What do the first Initial, CID rotation, and backend draining each require from load balancing?
- Why does a successful
--http3request reporting version 2 fail to prove the QUIC path works? - Which client and network evidence should be collected first after
--http3-onlyfails? - How should HTTP 425 be retried, and why must that retry avoid early data?