Nginx applies header compression asymmetrically in both HTTP/2 and HTTP/3. While clients compress repeated headers after the first transmission, the server always emits full literals, keeping response header sizes constant regardless of repetition count.
Measurements on a minimal stand with four identical requests show server responses locked at 131 bytes under HTTP/3 and 129 bytes under HTTP/2. Client requests, by contrast, drop from 246 bytes to 8 bytes once the dynamic table is populated.
Inspection of release-1.31.3 reveals the root cause inside ngx_http_v3_filter_module.c: all twenty-two invocations of ngx_http_v3_encode_field_ri and ngx_http_v3_encode_field_lri pass a zero value for the dynamic flag, forcing every field to be encoded as a literal. The field-section prefix is likewise emitted with insert_count and delta_base set to zero. No server-side encoder stream of type 0x02 is ever opened.
The identical limitation exists in the HPACK implementation. The HTTP/2 filter module contains zero references to ngx_http_v2_hpack_t and explicitly sends a Dynamic Table Size Update of zero on the first HEADERS frame, disabling client-side table usage for server responses.
The receiving path, however, is fully functional. ngx_http_v3_table.c implements insertion, eviction, blocked-stream tracking and the default 4096-byte table capacity announced in the SETTINGS frame. Debug logs confirm that client-supplied headers are correctly inserted and later referenced by index.
The asymmetry is explicitly allowed by RFC 9204 §4.2, yet it eliminates the compression gains that motivated the introduction of QPACK and HPACK. The unused encoder code path also contributed to a use-after-free vulnerability (CVSS 9.2) in the QPACK decoder earlier this year.


