Open-Source Wikis

/

nginx

/

Systems

/

HTTP/3

nginx/nginx

HTTP/3

Active contributors: Roman Arutyunyan, Sergey Kandaurov

Purpose

HTTP/3 (RFC 9114) running over QUIC. The HTTP/3 layer parses HTTP/3 frames, manages QPACK header compression, and bridges QUIC streams into the same ngx_http_request_t objects used by HTTP/1 and HTTP/2. The QUIC transport itself is in src/event/quic/ (see quic).

First mainline commit: 2020-03-13. The current implementation became non-experimental in the 1.25.x mainline.

Directory layout

src/http/v3/
├── ngx_http_v3.{c,h}              # module init + ngx_http_v3_module bootstrap
├── ngx_http_v3_module.c           # http3_* directives
├── ngx_http_v3_request.c          # the QUIC stream → ngx_http_request_t bridge
├── ngx_http_v3_filter_module.c    # output filter producing HTTP/3 frames
├── ngx_http_v3_parse.{c,h}        # frame + setting parsers
├── ngx_http_v3_encode.{c,h}       # field-line encoding for HEADERS frames
├── ngx_http_v3_table.{c,h}        # the QPACK dynamic table
└── ngx_http_v3_uni.{c,h}          # unidirectional control / encoder / decoder streams

Key abstractions

Type / function Role
ngx_http_v3_session_t Per-QUIC-connection HTTP/3 state (settings, QPACK tables, control streams)
ngx_http_v3_parse_t Frame parser state (one per request stream)
ngx_http_v3_init_request_stream() Called when a new bidirectional stream opens — bootstraps the request bridge
ngx_http_v3_init_uni_stream() Called for unidirectional control / encoder / decoder streams
ngx_http_v3_send_settings() Open the control stream, send SETTINGS

Stream model

QUIC has two stream flavors:

  • Bidirectional, client-initiated — one HTTP request/response pair per stream
  • Unidirectional — one of three reserved purposes:
    • Control stream (type 0x00) — SETTINGS, GOAWAY, MAX_PUSH_ID
    • QPACK encoder stream (type 0x02) — instructions to mutate the dynamic table
    • QPACK decoder stream (type 0x03) — header-block ack / cancellation

ngx_http_v3_uni.c reads the first byte of each unidirectional stream to determine type, then routes.

Frame types

Frame Direction Purpose
DATA both Request / response body
HEADERS both Header block (QPACK-encoded)
CANCEL_PUSH both Cancel a server push
SETTINGS control stream Connection settings
PUSH_PROMISE server → client (Unused — push not implemented client-side)
GOAWAY both Begin connection shutdown
MAX_PUSH_ID client → server Push limit

Request flow

sequenceDiagram
    participant QC as QUIC connection
    participant HS as ngx_http_v3_session_t
    participant SP as ngx_http_v3_parse
    participant RP as ngx_http_request_t
    participant Engine as Phase engine

    QC->>HS: stream opened (bidi, client-initiated)
    HS->>SP: init parse state for stream
    QC-->>SP: data chunks (frames + bytes)
    SP->>SP: parse HEADERS frame
    SP->>RP: ngx_http_v3_request_create + populate
    RP->>Engine: ngx_http_process_request
    Engine->>Engine: 11 phases as usual
    Engine->>RP: response headers + body
    RP->>SP: output via ngx_http_v3_filter
    SP-->>QC: frames written to stream

QPACK

QPACK (RFC 9204) is HTTP/3's header compression. Like HPACK it has a static table and a dynamic table, but unlike HPACK the dynamic table is shared across streams via a separate encoder stream that carries table-mutation instructions out-of-band. This decouples header block ordering from table updates so that head-of-line blocking is avoided.

src/http/v3/ngx_http_v3_table.c implements the dynamic table on both encode and decode sides. Inserts on the encoder stream are processed in arrival order; references in HEADERS blocks are resolved against a snapshot of the table.

Settings

ngx_http_v3_session_t tracks the negotiated values of:

  • SETTINGS_MAX_FIELD_SECTION_SIZE
  • SETTINGS_QPACK_MAX_TABLE_CAPACITY
  • SETTINGS_QPACK_BLOCKED_STREAMS

Defaults match the spec; nginx tunables exposed via http3_max_concurrent_streams, http3_max_field_size, http3_max_table_capacity.

Output

The HTTP/3 filter (ngx_http_v3_filter_module) replaces the chunked / write filter for H3 streams. It encodes response headers as a HEADERS frame (with QPACK), wraps body buffers in DATA frames, and pushes them onto the QUIC stream's send queue.

ALPN

H3 uses ALPN identifier h3. When a QUIC connection completes the TLS handshake with h3 selected, ngx_http_v3_init is the entry point. The HTTP/2 / HTTP/1.1 paths are not relevant here — H3 always runs over QUIC.

Integration points

  • QUIC transport (src/event/quic/) — the source of stream events. H3 doesn't read from c->fd; it reads from QUIC stream APIs.
  • HTTP request engine — the same 11 phases, filters, content handlers.
  • TLS / OpenSSL — handled by the QUIC layer using OpenSSL's keying APIs (and the OpenSSL compat shim when the linked OpenSSL doesn't expose them natively).
  • Alt-Svc headerquic_max_idle_timeout, quic_active_connection_id_limit, etc., advertised to clients through Alt-Svc from the HTTP/1.1 path.

Entry points for modification

The "narrow waist" between QUIC and HTTP is ngx_http_v3_request.c. If a feature requires changes in the request ↔ stream bridge (e.g., trailers, QUIC-specific abort semantics), this is the file. QPACK changes go in ngx_http_v3_table.c. SETTINGS or new frame types — ngx_http_v3_parse.c. The QUIC layer handles everything below the frame boundary.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

HTTP/3 – nginx wiki | Factory