~0x64616E69656C
nxTLS · experiment log · 2026

Questions first.
Evidence second.
Architecture later.

nxTLS uses focused experiments to answer one engineering question at a time against fixed, reviewable source inputs. The goal is not to prove old code right. It is to find out which ideas still survive contact with current nginx, BoringSSL and protocol behavior.

Current evidence set:
HTTP/2 response HPACK reuse · retained for first release
HTTP/3 response QPACK reuse · retained for first release
TLS record sizing · static 4 KiB records · integration boundary open
QUIC reload · merged fix in official 1.31.7; retested
HTTP/2 priority × TLS records · 240/240; live update traced
Priority-aware early flush · controlled prefix retry PASS; native gate open
Response HPACK × G0A · bounded integration verified; E7-2 open
pinned nginx 1.31.7 · BoringSSL 594a442… · experiment 07 checkpoint 2026-10-21 controlled spikes · no production adoption implied
EXPERIMENT INDEX / 07

Find the experiment.

One number per experiment. Summary jumps to the evidence on this page; Full report opens the detailed write-up. These are index numbers, not page-section numbers or production milestones.

EXP 01HTTP/2

Response HPACK

Bounded response-header reuse against a stock nginx control; revalidated on nginx 1.31.7.

EXP 02HTTP/3

Response QPACK

Dynamic-table response compression, decoder feedback and the limits of current runtime evidence.

EXP 03TLS 1.3 / TCP

TLS Record Sizing

Static 4 KiB records, nginx buffering and client-visible delivery trade-offs.

EXP 04HTTP/3

QUIC Reload

Independent before/after validation of the upstream reload fix, then a 1.31.7 retest.

EXP 05HTTP/2

HTTP/2 Priority & TLS Records

RFC 9218 scheduling versus actual delivery of critical DATA frames, across 240 trials.

EXP 06HTTP/2 / TLS 1.3

Priority-Aware Early Flush (G0A)

A short TLS record at the urgent DATA boundary, plus four intact controlled selected-prefix retries.

EXP 07HTTP/2 / RESEARCH IN PROGRESS

Response HPACK × Early Flush

Four-case HPACK/G0A integration: decoded responses and stateful safety controls verified; the combined latency question remains open.

METHOD

A spike answers a question. Nothing more.

The experiment log separates observed mechanism from product policy. Project-owned spikes start from the pinned baseline; an upstream validation instead fixes an exact before/after source pair. In both cases, the evidence must not become an architectural decision by accident.

01

Control

Pin exact source revisions. Use the nxTLS baseline for policy spikes and an explicit commit pair for upstream validation.

02

Isolate

Patch only the behavior under test. Historical code is evidence, not the implementation base.

03

Measure

Observe protocol bytes or TLS records directly rather than substituting proxy metrics for the thing being tested.

04

Stop

Document what worked, what did not, and which gate remains. A successful spike does not select a production design.

The old tree was the question generator. TinX supplied historical clues. Pinned nginx and BoringSSL supply the control. nxTLS supplies new experiments. An old engineering judgment can survive without the old implementation surviving with it.
UPSTREAM REFRESH · 2026-10-18

Same questions. New upstream.

Official nginx 1.31.7 and BoringSSL 594a44291086dcc7cef06cabec4c0b47afd674c0 replaced the previous pins. The canonical build, BoringSSL suites and four focused experiment regressions passed. The earlier reports remain dated evidence, not measurements retroactively reassigned to new source revisions.

HPACK · h2

440 responses

220 stock and 220 experimental HTTP/2 responses. Bounded reuse and fallback passed on nginx 1.31.7; 204 B remains 204 B in stock and falls to 107 B after the experimental first block.

QPACK · h3

560 responses

280 stock and 280 experimental HTTP/3 responses passed. The separate pinned PR #60 decoder accepts all five disputed RIC fixtures and 172/172 saved field sections; Debian 0.3.23 still fails the known cases.

TLS 1.3 · h1/h2

18/18 transport checks

Stock 16 KiB writes, 4 KiB buffering and static 4096-byte fragments preserved payloads and observed record boundaries. The small timing sample supports direction, not equivalence or tail behavior.

QUIC · reload

48/48 · 4/4 inflight

The unpatched nginx 1.31.7 release passed every fresh post-SIGHUP H3 probe and all four established transfers in the isolated IPv4, IPv6 and stacked-reload checks.

nginx pin1.31.7
BoringSSL native24/24 invocations
BoringSSL protocol11,076/11,076
baseline CTest1/1 PASS
Scope: these are bounded regression checks against the refreshed pinned baseline, not a new benchmark campaign or product release. This pin refresh did not benchmark RFC 9218 scheduling. The separate fifth experiment below subsequently measured nginx 1.31.7 prioritization and TLS-record delivery in a new, randomized campaign. Detailed provenance and limits: docs/UPSTREAM_REFRESH_2026-10-18.md.
EXP 01 / RESPONSE HPACK

The fix I never wrote down.

TinX carried a real response-side dynamic HPACK encoder. Years later, the surviving archive could prove the mechanism existed but could not prove that it caused the remembered rendering difference. nxTLS turned that memory into a current A/B question.

question

Does current nginx still leave response reuse on the table?

The original nginx 1.31.6 control dynamically decoded request HPACK, and some generated response fields could use incremental indexing, but nginx did not retain and reuse a persistent dynamic response table. The October 18 regression reproduced the same missing response reuse on stock 1.31.7.

The historical TinX encoder did retain connection state, insert and evict entries, and reuse complete name/value matches. That made it a useful clue, not code to port.

historical limit

The browser story is not preserved evidence.

The old production configuration, packet traces and controlled before/after browser measurements are gone. The exact 2018 runtime symptom behind the zero-table-size revert is also not preserved.

The modern experiment therefore measures HPACK behavior directly and makes no claim about page rendering, latency or the cause of the remembered difference.

control

No persistent response reuse.

Repeated response fields remain literal in stock nginx even when the peer advertises a nonzero decoder table. An explicit table-size setting only adds the expected table-size update behavior; it does not create encoder reuse.

experiment

Bounded indexed reuse works.

The isolated response encoder kept connection-level state, reused complete dynamic entries, handled dynamic names and exercised insertion, eviction and peer table-size changes without importing the TinX implementation.

result

204 once. 107 thereafter.

In the stable repeated-response fixture, the patched first block stayed at 204 bytes and the next 19 blocks were 107 bytes. The measurement is compressed HPACK block size, not end-to-end performance.

Gate C

The mechanism earned a design discussion.

The hard part is now state ownership and ordering: normal responses, generated fields, table-size changes and trailers must all agree on one connection-wide encoder history before production design can be considered.

Stock repeated block204 B
Patched repeated block107 B
Scoperesponse HPACK
StatusGate C
Why it matters: the experiment established that persistent response-side HPACK reuse is still technically relevant on the pinned nginx baseline. It did not establish a browser-performance gain or select the production patch shape.

EXP 01 · Response HPACK

The complete report contains the historical/current source comparison, runtime fixtures, implementation spike, table-size cases, churn behavior, validation and limitations.

experiments/hpack-response-encoding/README.md
EXP 02 / RESPONSE QPACK

Same policy question. Different state machine.

QPACK can pursue the same product goal — bounded reuse of safe response fields — but it is not HPACK with different opcodes. The encoder stream, decoder feedback, Required Insert Count, blocked-stream budget and reference lifetime are part of the mechanism.

control

Stock responses stay literal.

nginx 1.31.6 had dynamic QPACK machinery for request decoding, but no dynamic response encoder. The 1.31.7 stock regression likewise showed no persistent dynamic response reuse; normal responses, 103 and trailers still use its non-reusing response path.

experiment

Bounded response reuse works.

The spike added separate outbound encoder state, a server encoder stream, bounded feedback/reference tracking, eviction protection and literal fallback without changing the request decoder.

stable result

202 once. 104 thereafter.

With peer capacity 4096 and a nonzero blocked-stream allowance, stock produced 202-byte field sections throughout. The experimental path produced 202 bytes once and 104 bytes for the next 19 responses.

bounded degradation

Feedback can disappear without unbounded state.

With decoder feedback withheld, the experiment stopped taking dynamic-reference risks, retained bounded state and fell back to literals. Cancellation and Insert Count Increment paths were also exercised.

Stable stock total4040 B
Stable patched total2178 B
Repeated section202 → 104 B
StatusGate B
independent decoder validation

The decoder disagreement was fixed upstream.

The original extended 8192 / 4096 RIC-wrap diagnostic failed in Debian pylsqpack 0.3.23 but passed in nghttp3 1.17.0. The October 18 replay confirmed five failing boundary fixtures and 171/172 accepted captured sections in Debian's decoder.

The exact merged pylsqpack PR #60 commit 9e705dfeadeb… accepted all five and all 172 sections on identical saved bytes. PyPI 0.3.24 still lacks the fix. This resolves the specific decoder disagreement, not every possible QPACK interoperability concern.

what remains

Gate B stays closed.

The fixed-decoder replay does not promote the experimental response encoder to production design. Runtime evidence is still thin for delayed feedback under concurrency, stream backpressure, partial/error paths, loss/reordering and sustained load.

HPACK and QPACK may eventually share semantic policy. Their state machines and wire mechanisms remain separate.

Measurement boundary: the byte totals above are QPACK field-section bytes. Encoder-stream instructions, decoder feedback, HTTP/3 framing, QUIC and TLS overhead are separate costs; the experiment makes no latency or throughput claim.

EXP 02 · Response QPACK

The main report covers the response encoder and runtime suite. The decoder-validation note preserves the old Debian failure and documents the successful isolated upstream PR #60 replay.

experiments/qpack-response-encoding/README.md
experiments/qpack-response-encoding/DECODER_VALIDATION.md
EXP 03 / TLS RECORD SIZING

The record size was the signal.

TinX historically carried adaptive TLS record sizing. The first nxTLS transport campaign rejected the tested 1024/8192/16384 policy. A focused follow-up then separated nginx output buffering from TLS record sizing itself: could nginx keep its 16 KiB write boundary while BoringSSL emitted the same 4 KiB records?

mechanism proof

One 16 KiB write can become four 4 KiB TLS records.

With nginx left at a 16 KiB buffer, a post-handshake SSL_set_max_send_fragment(..., 4096) kept full successful SSL_write() calls at 16 KiB while BoringSSL emitted four 4 KiB plaintext records.

The stock 4 KiB-buffer case instead issued 4 KiB writes and emitted 4 KiB records. The follow-up therefore isolated write boundary from record boundary.

timing result

The smaller nginx buffer added no measured early advantage here.

Across HTTP/1.1, HTTP/2 and clean, WAN and adverse paths, the two 4 KiB-record cases differed by less than 0.1 ms in median first-decrypted, 4 KiB and 8 KiB delivery.

This is an already-available static-file result. Streaming and proxy-producer timing remain explicitly unmeasured.

Why 4 KiB? nginx defaults ssl_buffer_size to 16 KiB and documents 4 KiB as a smaller TTFB-oriented setting. Cloudflare's 2016 dynamic-record patch asked a more aggressive version of the same latency question. nxTLS first retested the adaptive idea, then isolated the mechanism. The current evidence points to static 4 KiB TLS records, not specifically to a 4 KiB nginx buffer. nginx docs ↗ · Cloudflare 2016 ↗
D · stock buffer

4 KiB write → 4 KiB record.

Stock nginx with ssl_buffer_size 4k produced full 4 KiB successful writes and full 4 KiB TLS plaintext records.

F · static fragment

16 KiB write → four 4 KiB records.

With nginx's 16 KiB buffer retained, the static BoringSSL fragment limit produced the same record granularity without shrinking nginx's write boundary.

Outcome A

Early delivery reproduced.

D/F median first-decrypted, 4 KiB and 8 KiB timings differed by less than 0.1 ms across both protocols and all three network paths.

integration open

Config or TLS policy is still a design choice.

The data support static 4 KiB TLS records for the tested objective. They do not yet choose between stock nginx configuration and an nxTLS-owned BoringSSL fragment policy.

nginx default buffer16 KiB
Measured record direction4 KiB
Static follow-up1,350 connections · 1,800 responses
Mechanism smoke12 connections · 16 responses
Two campaigns, one narrowing result: the earlier 4,920-connection campaign showed that the tested adaptive 1 KiB → 16 KiB policy bought a modest first-byte gain but delayed useful-body milestones and the measured HTTP/2 response chain. The 2026-10-02 follow-up then showed that static 4 KiB records reproduce the stock 4 KiB-buffer early-delivery behavior even when nginx continues to write 16 KiB at a time. Mixed loss-path tails and completion timings prevent a universal equivalence claim.

EXP 03 · TLS Record Sizing

The full report contains both campaigns: BoringSSL record controls, isolated TCP topology, adaptive-policy measurements, the static 4 KiB D/F mechanism proof, kernel revalidation, record/write traces, loss-path caveats and the still-open integration boundary.

experiments/tls-record-sizing/README.md
EXP 04 / QUIC RELOAD

The fix belongs upstream.

A separate question from our HPACK/QPACK policy spikes: can nginx preserve existing QUIC connections and accept new ones during graceful reload? We built the exact upstream commits before and after the merged QUIC BPF fix with the same pinned BoringSSL, then exercised real HTTP/3 across worker-generation transitions.

pre-merge control

New HTTP/3 connections fell through the gap.

With four workers, quic_bpf on and a running H3 transfer, the pre-merge upstream revision accepted only 13 of 32 fresh connections after SIGHUP. The existing transfer completed, but 19 new requests failed.

merged upstream result

32 of 32. Then IPv6 and stacked reloads.

The merged revision accepted 32/32 new IPv4 H3 connections. IPv6 also passed 32/32. Three successive IPv4 reloads passed 96/96 new requests, while all three overlapping slow transfers completed.

before · ce6e7d6c

13/32 fresh H3

Exact first parent of the upstream merge. Four nginx workers, BPF enabled, 19 failed post-reload requests.

after · 63af971a

32/32 fresh H3

Same BoringSSL, Clang, kernel and fixture. Existing long-running transfer also completed successfully.

IPv6

32/32 after merge

Isolated ::1 listener, one SIGHUP, valid new generation and completed inflight transfer.

stacked reloads

96/96 · 3/3 inflight

Three successive reloads with overlapping old-generation H3 transfers; all finished with their expected generation.

Upstream first, with evidence. The October 9 before/after comparison isolates nginx PR #1756. The October 18 retest used the official, now-pinned nginx 1.31.7 release: 48/48 new H3 connections after reload and 4/4 established transfers passed. Binary upgrades, BPF resource reclamation, migration and lossy networks remain outside these tests.

EXP 04 · QUIC Reload

Read the question, exact upstream SHAs, build provenance, test invariants, IPv4/IPv6 results, stacked worker-generation controls and explicitly excluded scenarios.

experiments/quic-reload · 2026-10-09 differential + 2026-10-18 release smoke · SIGHUP only
EXP 05 / HTTP/2 PRIORITY

Priority is a signal. Delivery is a queue.

A separate fifth experiment asks how quickly nginx 1.31.7's RFC 9218 scheduling reaches the client. The HTTP/2 queue, nginx SSL buffering, BoringSSL records and TCP output are different boundaries. Improving one is not proof that the others changed.

priority effect · stock TLS records

Critical DATA arrived 28–29 ms earlier.

With competing HTTP/2 streams on one TLS/TCP connection, explicit RFC 9218 urgency moved critical delivery forward by a paired median 27.8 ms (simultaneous) and 29.1 ms (late request) versus equal priorities. Aggregate completion was essentially unchanged.

transport boundary · static 4 KiB TLS

Smaller records helped, but not for free.

With RFC 9218 already active, 4 KiB records advanced the first complete critical DATA frame by another paired median 3.1 ms or 9.9 ms. The fixture emitted many more TLS records and finished slightly later overall. Without priority, small records even regressed simultaneous delivery.

primary matrix

240/240 runs

Two workloads, four factors (A/B/C/D), thirty randomized repeats per cell. Payload integrity, HTTP/2 flow control and record boundaries checked by the host harness.

priority · A→B

−27.8 / −29.1 ms

Paired median improvement for the first complete critical DATA frame in simultaneous / late-request workloads. Direction was consistent in all 30 paired repeats.

4 KiB · B→D

−3.1 / −9.9 ms

Additional paired median improvement with explicit priorities, at the cost of 387 / 192 additional TLS records (simultaneous / late).

live update · trace

Priority applied.

Independent instrumented controls confirmed u=5,i=1 to u=0,i=0 for an active stream, while bytes were already pending in SSL and TCP output.

Interpretation boundary: the host's aggregated 240-run statistics were independently reviewed; all individual primary samples were not replayed externally. The live trace confirms the priority-state update and pre-existing output backlog, not wholesale reordering of queued HTTP/2 frames or a general CPU improvement. No nxTLS TLS-record default changed.

EXP 05 · HTTP/2 Priority & TLS Records

The full report separates client-visible DATA bytes from complete hyper-h2 frames, A→B scheduling from B→D record effects, and primary performance runs from the instrumented PRIORITY_UPDATE control.

experiments/http2-priority-record-delivery/RESULTS.md · 2026-10-18 · no production integration
EXP 06 / EARLY FLUSH · G0A

The frame was ready. The record was not.

Priority-Aware Dynamic TLS Record Sizing began as a question about changing fragment sizes using HTTP/2 urgency. Source tracing pointed instead to a narrower boundary: end one nginx output batch immediately after an eligible small urgency-0 DATA frame, then resume normal bulk output. The candidate is called G0A.

G0A · mechanism observed

A short record when the critical frame is ready.

In two trace-enabled observations, G0A emitted 4,381- and 4,241-byte TLS writes. The client decrypted complete critical DATA frames earlier than with B in those samples, while later bulk writes returned to 16 KiB. The extra cost was only two and one TLS records respectively. No H2 frame reordering or BoringSSL fragment-policy change was needed.

Phase 3E · controlled prefix retry PASS

The prefix blocked. G0A recovered.

Four deliberately blocked selected-prefix writes produced real BoringSSL SSL_ERROR_WANT_WRITE through a test BIO. Retry inputs, frame ordering and response integrity remained correct. The original G0A patch did not change. A native socket prefix block is still unobserved.

configured stock

300/300

Randomized B/H/D/S/SH trials. Stock 4 KiB SSL buffer plus 4 KiB H2 chunks gave the lowest observed median critical-DATA latency, but required many more TLS records than B.

selected G0A boundary

4,381 / 4,241 B

Client-confirmed short TLS records in two instrumented single-run observations; +2 / +1 records over B, not a statistically demonstrated latency win.

Phase 3E · selected-prefix retry

4/4 PASS

Controlled BoringSSL WANT_WRITE in both workloads and two injected transport states; 20/20 responses intact across diagnostic transfers.

native prefix coverage

INCONCLUSIVE

All four native B/G transfers completed; neither selected G0A prefix blocked. Fresh-record and native socket retry remain separate gates.

Research result: Priority-informed early TLS emission is demonstrated with minimal additional records. Phase 3E now confirms correct selected-prefix recovery in four test-BIO-induced BoringSSL WANT_WRITE cases, with the original patch unchanged. Those cases began with pending TLS output and used synthetic event rearming: native prefix and fresh-record coverage remain open. Timings are single trace samples, not a performance campaign; D-015 is unchanged.

EXP 06 · Priority-Aware Early Flush (G0A)

The full report documents the discovered output boundary, short TLS records and Phase 3E's four successful controlled prefix retries, alongside the independent native-socket and fresh-record gaps.

experiments/priority-aware-dynamic-tls-record-sizing/README.md · PHASE3E_2026-10-21.md
unchanged stage-only nginx 1.31.7 patch · pinned BoringSSL · no production adoption
EXP 07 / HPACK × G0A

Two mechanisms. One output path.

Does shrinking response HEADERS make the fully decoded critical answer arrive sooner when G0A is already selecting an early TLS-output boundary? EXP 07 separates compression, priority-aware output and their interaction with four controlled builds.

E7-1 / E7-2A · PASS

HPACK and G0A survived the same response.

The combined PG build preserved HPACK table progress, HTTP/2 frame order, exact bodies and G0A's selected short TLS write across the tested table sizes and receive pause. In a warm reuse example, HEADERS fell from 241 to 144 bytes.

E7-2B · INCONCLUSIVE

Stateful controls passed; one lifecycle gate did not land.

SETTINGS 4096 → 0 → 4096, cancellation and bounded parallel controls produced no reported integrity defect. The server-side graceful GOAWAY fixture did not exercise the intended master/worker drain and remains open.

integration

8/8 cells

E7-1; 52 complete responses across the B/P/G/PG variants.

bounded safety

24/24 cells

E7-2A; 156 complete responses with correct HEADERS, payload hashes and TLS accounting.

stateful controls

376 responses

E7-2B; 32 cells, 48 connections, 12 intentional cancellations. G2 remains open.

client-visible performance

NOT MEASURED

E7-3 is blocked. The additive or overlapping latency effect has not been established.

What is established: HPACK can change the size of a G0A-selected short write without breaking the tested response stream. What is not established: that combining the mechanisms improves time-to-complete-response over either feature independently, or that all outstanding safety gates have passed.

EXP 07 · Response HPACK × Early Flush

A dated research checkpoint describing the four-case experiment, bounded HPACK state/backpressure controls, observed byte-boundary interaction, the separate G0A prefix-retry evidence and the remaining GOAWAY and performance gates.

experiments/hpack-priority-early-flush/ · 2026-10-21 · research in progress · production unchanged
NEXT QUESTIONS

Progress means narrowing the unknowns.

The current experiment set does not ask for a nostalgia merge. It narrows the next questions enough that future work can stay small, reviewable and explicit.

HPACK

Retained for the first Debian release.

The current response-reuse direction stays as-is. Further experiment redesign and tuning are deferred until integration work gives a concrete reason to reopen it.

QPACK

Retained for the first Debian release.

The current response encoder and decoder-validation direction stays intact. Its protocol-specific state machine remains separate from HPACK even where semantic policy may converge.

TLS records

Static 4 KiB baseline; selective early flush demonstrated.

The historical 1024/8192/16384 adaptive policy remains rejected. Stock 4 KiB settings improved critical-frame arrival in the measured workloads, at a much higher record count. G0A demonstrates a different trade-off: one priority-aware short output boundary, then normal bulk writes. It remains experimental.

project rule

Shared policy before shared implementation.

HPACK and QPACK may converge on which fields are eligible, sensitive or budgeted. Their protocol mechanisms should remain independently correct.

Back to the project overview.

The experiment log tracks evidence and progress. Architecture, crypto policy, configuration, lineage and the broader roadmap remain on the main nxTLS page.

project → evidence → implementation decision
nxTLS overview →