Learning objective. Distinguish the two scopes of HTTP/3 failure (resetting a single stream versus tearing down the whole connection) and read the graceful-shutdown and error-code semantics that decide which one you are looking at.
Two scopes of failure #
HTTP/3 errors come in exactly two blast radii, and telling them apart is most of the diagnosis:
- Stream errors end one request; the connection and every other stream keep going.
- Connection errors end everything, via a QUIC
CONNECTION_CLOSEcarrying an HTTP/3 error code.
Both draw from the same HTTP/3 error-code space (constants.md §12), so the code alone does
not tell you the scope — the frame that carries it does.
Resetting a single stream #
Cancelling a request resets its stream and nothing else. Either side may do it (a client
whose response is no longer of interest, or a server that cannot process the request) by
abruptly terminating the stream ([RFC 9114 §4.1.2]): a RESET_STREAM on its own send side
and a STOP_SENDING on its receive side (§4.1), carrying an HTTP/3 error code
such as H3_REQUEST_CANCELLED (0x010c). The transport delivers the reset; the peer stops
work on that stream and reclaims its resources; every other request continues. This is the
common, benign case: a user navigating away mid-download produces exactly this.
Graceful shutdown with GOAWAY #
An endpoint that wants to stop without dropping in-flight work sends a GOAWAY frame
on its control stream to begin a graceful shutdown ([RFC 9114 §5.2]). A server's GOAWAY
carries a client-initiated bidirectional stream ID; a client's carries a push ID. The
identifier draws an explicit line: "requests or pushes with the indicated identifier or
greater are rejected" by the sender ([RFC 9114 §5.2]), and are therefore safe to retry on a
fresh connection. Requests below it may already have been processed and are given the
chance to finish (their outcome is known only when a response, reset, or lower GOAWAY
arrives). The endpoint then closes, ideally with H3_NO_ERROR. This
is how a server drains connections for a deploy or restart without failing active requests —
the difference between a rolling update users never notice and a wave of reset connections.
Connection errors and critical streams #
Some failures are not survivable and take the whole connection down with a CONNECTION_CLOSE
and an HTTP/3 error code ([RFC 9114 §8]):
- Framing and protocol violations. An invalid frame sequence is
H3_FRAME_UNEXPECTED(0x0105, §5.1); a malformed or misplaced SETTINGS isH3_SETTINGS_ERROR(0x0109); a missing SETTINGS as the first control-stream frame isH3_MISSING_SETTINGS(0x010a). - Closing a critical stream. The control stream and the QPACK encoder/decoder streams
must stay open for the connection's life; closing one is
H3_CLOSED_CRITICAL_STREAM(0x0104, [RFC 9114 §6.2.1]).
These map onto the QUIC error machinery of §2.5: the HTTP/3 code travels in a
QUIC CONNECTION_CLOSE of type 0x1d (application), and you read it the same way you read a
transport error: find the CONNECTION_CLOSE, read the code, look it up.
A stream reset is not a connection error, and conflating them is a classic
misdiagnosis. A trace full of H3_REQUEST_CANCELLED resets on individual streams is usually
healthy application behavior (cancellations, timeouts, navigations); a single
CONNECTION_CLOSE with H3_FRAME_UNEXPECTED is a bug. The scope, read from the carrying
frame, matters more than the code.
editorial Wire GOAWAY into your deployment lifecycle: a server that just stops accepting connections mid-request produces user-visible failures, while one that sends GOAWAY and drains cleanly does not. On the debugging side, when an HTTP/3 connection "drops," first classify the ending: a per-stream RESET_STREAM (one request died), a GOAWAY-then-close (intentional drain), or a CONNECTION_CLOSE with a protocol error code (a real bug, often an interop mismatch, §13.2). The three have completely different causes and fixes.
Takeaways #
HTTP/3 fails at one of two scopes: a stream reset (one request, via RESET_STREAM/STOP_SENDING with a code like H3_REQUEST_CANCELLED) or a connection error (everything, via CONNECTION_CLOSE with a code like H3_FRAME_UNEXPECTED), with GOAWAY providing an orderly drain in between. The carrying frame, not the code, tells you the blast radius. §5.5 makes all of this concrete by building a small HTTP/3 client and watching its frames and errors on the wire.