Skip to content
github-actions[bot] edited this page Sep 9, 2026 · 210 revisions

Performance

Why this comparison is fair

These tables are a same-harness, same-origin, same-runner-class reverse-proxy comparison — not a blog-post bake-off of mismatched labs.

  • Same load generator (dotnet-httpclient), same origin process, same warmup/measure (2s / 8s), same concurrency ramp (8, 16, 32, 64), median of 3 GitHub Actions repeats.
  • Every reverse arm is three OS processes (load generator + origin child + proxy child). Origin-direct omits the proxy. Peers are never in-process with the client.
  • Same runner class per table: windows-latest / ubuntu-latest / macos-15-intel (4-core-class). Laptop High-perf and macos-latest are never mixed into these tables.
  • YARP is Yarp.ReverseProxy 2.3.0 with equivalent TLS/ALPN. nginx uses streaming knobs (keepalive 256, buffering off). HAProxy (GPL v2 Community) and Envoy (Apache 2.0) are open-source terminate peers on the same loopback shape — Linux/macOS only; Windows cells are Not possible (no official HAProxy port; Envoy Windows support discontinued). Fair knobs: HAProxy http-reuse aggressive, maxconn 256, nbthread=CPU; Envoy concurrency=CPU, cluster limits ~256, no access log, upstream TLS verify off for loopback CA. Linux nginx (mainline + http_v3) is authoritative; Windows nginx has no QUIC — those cells are Not possible, not losses.
  • MITM is TWP-only (CONNECT + forged certs). nginx/YARP cannot MITM; Lite/Full tables are overhead vs TWP reverse, not vs peers. There are no MITM charts.
  • The product signal is TWP÷YARP (gated ≥ 0.75 reverse) and MITM÷Reverse (Lite ≥ 0.50, Full ≥ 0.50), not absolute RPS. nginx / HAProxy / Envoy are wiki and chart peers only — no CI gate. Absolute RPS moves with runner heat; ratios are the claim.
  • Product fast paths (session-lite, skip-poll, compressed relay) are in-tree defaults for interception-off reverse — the same class of work YARP/nginx do. The harness does not disable TWP safety that peers also skip, and it does not retune to pass gates (PERF-GATES.md).
  • MITM leaf keys are a shared RSA/ECDSA pair per CertificateManager (one key for all forged leaves). That is an intentional RPS tradeoff: compromise of that material affects every host minted by that manager. Reverse/YARP/nginx paths do not mint leaves.

Numbers below are Release measurements with RpsLoadProbe. Native terminate peers: nginx, HAProxy, Envoy (where the OS supports them).

For pooling knobs and certificate first-visit tuning, see Performance and pooling. For the local cool A/B lab, laptop tables, and profiling notes, see Performance Profiling.

Contents

Measurement environment

All three OS use the 4-core-class public-repo GitHub-hosted runners: Windows / Linux at 4 vCPU / 16 GiB / 14 GB SSD, macOS at macos-15-intel 4-core / 14 GB (not macos-latest). Same harness knobs (workflow_dispatch RPS saturation: warmup 2s / measure 8s; concurrency 8, 16, 32, 64; median of 3 repeats; --stop-on-slo-fail default on). Every --ramp arm is three OS processes (parent load generator + origin child + proxy child), except origin-direct arms (load gen + origin only). Prefer TWP÷YARP / TWP÷nginx ratios over absolute RPS.

Laptop High-perf / cool-paired Windows numbers live on Performance Local Lab. Do not mix those absolutes into the tables below.

Tiered cadence

Tier Mode When
Daily / per-PR compare-spot minutes
Milestone compare-terminate / compare-matrix ~1–2h
Editions compare-editions ~60 min (CLI / Plus / Intercept stress arms)
Cross-version (Gate 2) compare-cross-version ~1–2h vs committed 6.0 baselines
Release / wiki compare-product ~3–4h
Heavier tables compare-bodies / post / lossy / arch / bridges / tls-cost dispatch independently

See PERF-GATES.md.

Windows (GitHub-hosted windows-latest)

OS Windows Server (GitHub-hosted windows-latest)
CPU 4 logical processors
RAM 16 GiB
Runtime .NET 10.0.x
nginx nginx/Windows 1.31.3 (same-OS only; no QUIC)
HAProxy Not possible on Windows (no official port)
Envoy Not possible on Windows (upstream discontinued Windows builds)
YARP Yarp.ReverseProxy 2.3.0
Harness RpsLoadProbe Release; median of 3 repeats

Linux (GitHub-hosted ubuntu-latest)

OS Ubuntu 24.04.x LTS
CPU 4 logical processors (AMD EPYC; runners this pass were 7763 / 9V74)
RAM 16 GiB
Runtime .NET 10.0.11
nginx nginx/1.31.4 (nginx.org mainline, --with-http_v3_module)
HAProxy HAProxy 3.2.23 built with USE_QUIC (GHA; Ubuntu distro 2.8 is not QUIC-capable)
Envoy pinned GitHub release static binary 1.36.7 (HTTP/3 compiled in)
YARP Yarp.ReverseProxy 2.3.0
Harness RpsLoadProbe Release; median of 3 repeats where noted

macOS (GitHub-hosted macos-15-intel)

OS macOS 15 (GitHub-hosted macos-15-intel, Intel x86_64)
CPU 4 logical processors
RAM 14 GB
Runtime .NET 10.0.x
nginx Homebrew nginx with --with-http_v3_module (workflow fails if missing)
HAProxy Homebrew haproxy (GHA install; bottles typically include USE_QUIC)
Envoy Homebrew envoy when available (GHA install; HTTP/3 compiled in)
MsQuic Homebrew libmsquic + openssl@3 on DYLD_LIBRARY_PATH / DYLD_FALLBACK_LIBRARY_PATH (QuicListener.IsSupported)
YARP Yarp.ReverseProxy 2.3.0
Harness RpsLoadProbe Release; median of 3 repeats where noted

Do not use macos-latest (Apple Silicon, 3-core / 7 GB) for publishable saturation numbers.

Saturation control

Calibration for the shared 4 vCPU loopback shape: how close client + origin are to saturated before ranking reverse peers. Tiny keep-alive GET. Median of 3 repeats @ 84b225f7 — 34355140813. Warmup 2s / measure 8s; concurrency 8, 16, 32, 64. Block A % of origin-HttpClient uses median peak RPS. Blocks B/C use peer÷YARP / ÷nginx on median peak (not % of H1 origin). RPS cells embed median RSS / CPU for the proxy child plus its full descendant tree (serve-proxy → nginx master → workers); origin-direct samples the origin child. Product matrices below use matched dotnet-httpclient only (not bombardier).

pwsh tools/RpsLoadProbe/run-rps.ps1 -Mode compare-saturation

Block A — H1 plain

Arm Generator Notes
origin-direct dotnet-httpclient No proxy — origin ceiling under the same client used for publishable ratios
origin-direct-bombardier bombardier External client check (CI installs bombardier)
bare-reverse-http1 dotnet-httpclient Thin C# H1 reverse (BareHttp1ReverseProxy) — .NET runtime / loopback ceiling for a three-process reverse hop; not a product peer
nginx-reverse-http1 / yarp-reverse-http1 / twp-reverse-http1 dotnet-httpclient Product peers (medals among these three only)

Windows (windows-latest)

Arm Generator Sustain Peak % of origin-HttpClient
origin-direct dotnet-httpclient 50,764
(55 MiB / 41.8% CPU)
50,764
(55 MiB / 41.8% CPU)
100.0%
origin-direct-bombardier bombardier 39,174
(55 MiB / 22.4% CPU)
39,174
(55 MiB / 22.4% CPU)
77.2%
bare-reverse-http1 dotnet-httpclient 25,470
(55 MiB / 46.2% CPU)
25,470
(55 MiB / 46.2% CPU)
50.2%
nginx-reverse-http1 dotnet-httpclient 13,510
(123 MiB / 24.9% CPU)
13,510
(123 MiB / 24.9% CPU)
26.6%
yarp-reverse-http1 dotnet-httpclient 21,164
(88 MiB / 51.0% CPU)
21,164
(88 MiB / 51.0% CPU)
41.7%
twp-reverse-http1 dotnet-httpclient 🥇 24,934
(76 MiB / 50.3% CPU)
24,934
(76 MiB / 50.3% CPU)
49.1%

Linux (ubuntu-latest)

Arm Generator Sustain Peak % of origin-HttpClient
origin-direct dotnet-httpclient 72,859
(80 MiB / 42.7% CPU)
72,859
(80 MiB / 42.7% CPU)
100.0%
origin-direct-bombardier bombardier 41,914
(80 MiB / 34.1% CPU)
41,914
(80 MiB / 34.1% CPU)
57.5%
bare-reverse-http1 dotnet-httpclient 32,705
(68 MiB / 45.5% CPU)
32,705
(68 MiB / 45.5% CPU)
44.9%
nginx-reverse-http1 dotnet-httpclient 🥇 38,533
(74 MiB / 40.8% CPU)
38,533
(74 MiB / 40.8% CPU)
52.9%
yarp-reverse-http1 dotnet-httpclient 27,832
(113 MiB / 50.0% CPU)
27,832
(113 MiB / 50.0% CPU)
38.2%
twp-reverse-http1 dotnet-httpclient 31,714
(83 MiB / 50.2% CPU)
31,714
(83 MiB / 50.2% CPU)
43.5%

Reverse peers are about 50–46% of the origin-direct HttpClient peak on this runner class (Win TWP 50.0%, Lin TWP 45.6%). Prefer the % column over absolute RPS across runs. Bare and origin-direct are controls (not medal peers).

Block B — H2 TLS→H1

Peer ratios (÷YARP / ÷nginx) on median peak; RPS cells embed (MiB / CPU%).

Windows (windows-latest)

Arm Generator Sustain Peak ÷YARP ÷nginx
nginx-reverse-http2 dotnet-httpclient 8,246
(139 MiB / 24.6% CPU)
8,246
(139 MiB / 24.6% CPU)
0.28× 1.00×
yarp-reverse-http2 dotnet-httpclient 29,174
(91 MiB / 53.6% CPU)
29,174
(91 MiB / 53.6% CPU)
1.00× 3.54×
twp-reverse-http2-cleartext dotnet-httpclient 🥇 34,778
(102 MiB / 51.5% CPU)
34,778
(102 MiB / 51.5% CPU)
1.19× 4.22×

Linux (ubuntu-latest)

Arm Generator Sustain Peak ÷YARP ÷nginx
nginx-reverse-http2 dotnet-httpclient 14,831
(98 MiB / 19.4% CPU)
14,831
(98 MiB / 19.4% CPU)
0.51× 1.00×
yarp-reverse-http2 dotnet-httpclient 29,012
(122 MiB / 49.7% CPU)
29,012
(122 MiB / 49.7% CPU)
1.00× 1.96×
twp-reverse-http2-cleartext dotnet-httpclient 🥇 33,240
(120 MiB / 51.6% CPU)
33,240
(120 MiB / 51.6% CPU)
1.15× 2.24×

Block C — H3→H1

Same layout as Block B. Requires QuicListener. nginx needs http_v3_module (Windows nginx has no QUIC). HAProxy needs USE_QUIC (GHA Linux compiles it; Homebrew typically has it). Envoy official binaries include HTTP/3.

Windows (windows-latest)

Arm Generator Sustain Peak ÷YARP ÷nginx
nginx-reverse-http3-cleartext dotnet-httpclient Not possible (no QUIC) Not possible (no QUIC) — —
yarp-reverse-http3-cleartext dotnet-httpclient 🥇 14,767
(156 MiB / 50.0% CPU)
14,767
(156 MiB / 50.0% CPU)
1.00× —
twp-reverse-http3-cleartext dotnet-httpclient 14,262
(104 MiB / 46.6% CPU)
14,262
(104 MiB / 46.6% CPU)
0.97× —

Linux (ubuntu-latest)

Arm Generator Sustain Peak ÷YARP ÷nginx
nginx-reverse-http3-cleartext dotnet-httpclient 0
(105 MiB / 22.5% CPU)
14,988
(105 MiB / 22.5% CPU)
0.86× 1.00×
yarp-reverse-http3-cleartext dotnet-httpclient 17,479
(184 MiB / 49.8% CPU)
17,479
(184 MiB / 49.8% CPU)
1.00× 1.17×
twp-reverse-http3-cleartext dotnet-httpclient 🥇 19,350
(144 MiB / 49.3% CPU)
19,350
(144 MiB / 49.3% CPU)
1.11× 1.29×

How to read the tables

  • Reverse = bare transparent fixed-forward (no TWP plugins / interception). nginx knobs match TWP/YARP streaming (keepalive 256, proxy_buffering off). MITM = TWP-only table on the same Client×Origin wires: Lite = no-op handlers (unchanged-lite finish reuses reverse compressed relay); Full = mutating handlers that append up to four unique headers per direction (RPS harness adds one; product uses MitmCompressedRelayHelper — no probe name in library code). Remove/replace/non-unique header growth and body mutation still force full decode/re-encode. nginx/YARP cannot MITM. HTTP/3 has no cleartext client (QUIC always encrypted).
  • Sustainable = last concurrency that still met error/latency SLOs. Peak = highest RPS in that ramp.
  • 🥇 = best among TWP / nginx / YARP on Reverse rows (or saturation blocks): highest RPS; on an RPS tie, lower Memory (RSS) then lower CPU%. MITM is TWP-only. Lite÷Reverse / Full÷Reverse = TWP MITM lite or full sustain ÷ TWP Reverse sustain on the same Client×Origin from the same compare-product job.
  • Not possible = product cannot do that path. Not measured = path exists but no published number yet for that OS.
  • Product refresh: compare-product @ 84b225f7 — 34355136373. Heavier/saturation/tls:
pwsh tools/RpsLoadProbe/run-rps.ps1 -Mode compare-product
pwsh tools/RpsLoadProbe/run-rps.ps1 -Mode compare-bodies
pwsh tools/RpsLoadProbe/run-rps.ps1 -Mode compare-post
pwsh tools/RpsLoadProbe/run-rps.ps1 -Mode compare-lossy
pwsh tools/RpsLoadProbe/run-rps.ps1 -Mode compare-tls-cost
pwsh tools/RpsLoadProbe/run-rps.ps1 -Mode compare-arch
pwsh tools/RpsLoadProbe/run-rps.ps1 -Mode compare-saturation

Windows — Titanium vs nginx vs HAProxy vs Envoy vs YARP

Client / origin: HTTP version and whether TLS is used (plain = cleartext, TLS = encrypted, QUIC = HTTP/3).

Reverse

Median of 3 repeats on windows-latest (4 vCPU / 16 GiB). Bare reverse 5×5 @ 84b225f7 — compare-product 34355136373. Warmup 2s / measure 8s; concurrency 8, 16, 32, 64. Prefer TWP÷peer ratios over absolute RPS. RPS cells include median RSS / CPU at the peak-RPS step as <br><sub>(MiB / CPU%)</sub>. nginx terminate peers use keepalive 256 + streaming buffers. HAProxy / Envoy are Linux-only peers (no official Windows port). Laptop High-perf / cool-paired numbers stay on the local lab.

Load generators: Reverse inbound H3 arms use dotnet-httpclient (http_version=3.0, RequestVersionExact). nginx/Windows is same-OS only (no QUIC). HAProxy/Envoy are Linux-only terminate peers.

Windows reverse

Client Origin TWP sustain TWP peak nginx sustain nginx peak HAProxy sustain HAProxy peak Envoy sustain Envoy peak YARP sustain YARP peak
HTTP/1 · plain HTTP/1 · plain 🥇 36826
(79 MiB / 48.2% CPU)
🥇 36826
(79 MiB / 48.2% CPU)
27338
(124 MiB / 24.8% CPU)
27338
(124 MiB / 24.8% CPU)
Not possible Not possible Not possible Not possible 33052
(87 MiB / 49.2% CPU)
33052
(87 MiB / 49.2% CPU)
HTTP/1 · plain HTTP/1 · TLS 🥇 31728
(90 MiB / 48.2% CPU)
🥇 31728
(90 MiB / 48.2% CPU)
16932
(134 MiB / 24.6% CPU)
16932
(134 MiB / 24.6% CPU)
Not possible Not possible Not possible Not possible 29836
(98 MiB / 45.9% CPU)
29836
(98 MiB / 45.9% CPU)
HTTP/1 · plain HTTP/2 · plain 🥇 50143
(110 MiB / 47.8% CPU)
🥇 50143
(110 MiB / 47.8% CPU)
Not possible Not possible Not possible Not possible Not possible Not possible 46652
(97 MiB / 49.7% CPU)
46652
(97 MiB / 49.7% CPU)
HTTP/1 · plain HTTP/2 · TLS 🥇 44922
(124 MiB / 46.5% CPU)
🥇 44922
(124 MiB / 46.5% CPU)
Not possible Not possible Not possible Not possible Not possible Not possible 42567
(96 MiB / 48.3% CPU)
42567
(96 MiB / 48.3% CPU)
HTTP/1 · plain HTTP/3 · QUIC 22817
(111 MiB / 50.2% CPU)
22817
(111 MiB / 50.2% CPU)
Not possible (no QUIC) Not possible (no QUIC) Not possible Not possible Not possible Not possible 🥇 23705
(123 MiB / 50.5% CPU)
🥇 23705
(123 MiB / 50.5% CPU)
HTTP/1 · TLS HTTP/1 · plain 🥇 32185
(91 MiB / 49.3% CPU)
🥇 32185
(91 MiB / 49.3% CPU)
18207
(140 MiB / 24.6% CPU)
18207
(140 MiB / 24.6% CPU)
Not possible Not possible Not possible Not possible 29623
(100 MiB / 50% CPU)
29623
(100 MiB / 50% CPU)
HTTP/1 · TLS HTTP/1 · TLS 🥇 30393
(85 MiB / 46.9% CPU)
🥇 30393
(85 MiB / 46.9% CPU)
13608
(142 MiB / 24.9% CPU)
13608
(142 MiB / 24.9% CPU)
Not possible Not possible Not possible Not possible 27048
(104 MiB / 49% CPU)
27048
(104 MiB / 49% CPU)
HTTP/1 · TLS HTTP/2 · plain 🥇 42478
(121 MiB / 49% CPU)
🥇 42478
(121 MiB / 49% CPU)
Not possible Not possible Not possible Not possible Not possible Not possible 39367
(112 MiB / 49.8% CPU)
39367
(112 MiB / 49.8% CPU)
HTTP/1 · TLS HTTP/2 · TLS 🥇 38546
(126 MiB / 45.6% CPU)
🥇 38546
(126 MiB / 45.6% CPU)
Not possible Not possible Not possible Not possible Not possible Not possible 36912
(106 MiB / 48.7% CPU)
36912
(106 MiB / 48.7% CPU)
HTTP/1 · TLS HTTP/3 · QUIC 🥇 20778
(115 MiB / 50.7% CPU)
🥇 20778
(115 MiB / 50.7% CPU)
Not possible (no QUIC) Not possible (no QUIC) Not possible Not possible Not possible Not possible 20571
(130 MiB / 49.7% CPU)
20571
(130 MiB / 49.7% CPU)
HTTP/2 · plain HTTP/1 · plain 🥇 46604
(94 MiB / 51.2% CPU)
🥇 46604
(94 MiB / 51.2% CPU)
Not possible Not possible Not possible Not possible Not possible Not possible 45286
(87 MiB / 49.7% CPU)
45286
(87 MiB / 49.7% CPU)
HTTP/2 · plain HTTP/1 · TLS 🥇 41209
(112 MiB / 50.2% CPU)
🥇 41209
(112 MiB / 50.2% CPU)
Not possible Not possible Not possible Not possible Not possible Not possible 38993
(92 MiB / 50.2% CPU)
38993
(92 MiB / 50.2% CPU)
HTTP/2 · plain HTTP/2 · plain 🥇 118838
(58 MiB / 30.7% CPU)
🥇 118838
(58 MiB / 30.7% CPU)
Not possible Not possible Not possible Not possible Not possible Not possible 69500
(97 MiB / 49.1% CPU)
69500
(97 MiB / 49.1% CPU)
HTTP/2 · plain HTTP/2 · TLS 🥇 101514
(64 MiB / 28% CPU)
🥇 101514
(64 MiB / 28% CPU)
Not possible Not possible Not possible Not possible Not possible Not possible 61945
(99 MiB / 49.1% CPU)
61945
(99 MiB / 49.1% CPU)
HTTP/2 · plain HTTP/3 · QUIC 🥇 33632
(128 MiB / 51% CPU)
🥇 33632
(128 MiB / 51% CPU)
Not possible (no QUIC) Not possible (no QUIC) Not possible Not possible Not possible Not possible 32494
(126 MiB / 51.8% CPU)
32494
(126 MiB / 51.8% CPU)
HTTP/2 · TLS HTTP/1 · plain 🥇 45454
(104 MiB / 51% CPU)
🥇 45454
(104 MiB / 51% CPU)
17529
(139 MiB / 24.2% CPU)
17529
(139 MiB / 24.2% CPU)
Not possible Not possible Not possible Not possible 42007
(93 MiB / 50% CPU)
42007
(93 MiB / 50% CPU)
HTTP/2 · TLS HTTP/1 · TLS 🥇 40127
(107 MiB / 49.5% CPU)
🥇 40127
(107 MiB / 49.5% CPU)
12976
(142 MiB / 24.4% CPU)
12976
(142 MiB / 24.4% CPU)
Not possible Not possible Not possible Not possible 36749
(100 MiB / 51.3% CPU)
36749
(100 MiB / 51.3% CPU)
HTTP/2 · TLS HTTP/2 · plain 🥇 108930
(76 MiB / 29.9% CPU)
🥇 108930
(76 MiB / 29.9% CPU)
Not possible Not possible Not possible Not possible Not possible Not possible 60474
(97 MiB / 51.8% CPU)
60474
(97 MiB / 51.8% CPU)
HTTP/2 · TLS HTTP/2 · TLS 🥇 97196
(75 MiB / 30% CPU)
🥇 97196
(75 MiB / 30% CPU)
Not possible Not possible Not possible Not possible Not possible Not possible 56030
(110 MiB / 50.7% CPU)
56030
(110 MiB / 50.7% CPU)
HTTP/2 · TLS HTTP/3 · QUIC 🥇 32998
(145 MiB / 51.2% CPU)
🥇 32998
(145 MiB / 51.2% CPU)
Not possible (no QUIC) Not possible (no QUIC) Not possible Not possible Not possible Not possible 29059
(130 MiB / 52.7% CPU)
29059
(130 MiB / 52.7% CPU)
HTTP/3 · QUIC HTTP/1 · plain 🥇 22862
(116 MiB / 41.5% CPU)
🥇 22862
(116 MiB / 41.5% CPU)
Not measured Not measured Not possible Not possible Not possible Not possible 21476
(146 MiB / 46.2% CPU)
21476
(146 MiB / 46.2% CPU)
HTTP/3 · QUIC HTTP/1 · TLS 🥇 19828
(118 MiB / 44.4% CPU)
🥇 19828
(118 MiB / 44.4% CPU)
Not measured Not measured Not possible Not possible Not possible Not possible 19031
(152 MiB / 50.1% CPU)
19031
(152 MiB / 50.1% CPU)
HTTP/3 · QUIC HTTP/2 · plain 🥇 38306
(152 MiB / 46.7% CPU)
🥇 38306
(152 MiB / 46.7% CPU)
Not possible (no H3 to H2) Not possible (no H3 to H2) Not possible Not possible Not possible Not possible 30912
(159 MiB / 46.7% CPU)
30912
(159 MiB / 46.7% CPU)
HTTP/3 · QUIC HTTP/2 · TLS 🥇 35523
(141 MiB / 45.7% CPU)
🥇 35523
(141 MiB / 45.7% CPU)
Not possible (no H3 to H2) Not possible (no H3 to H2) Not possible Not possible Not possible Not possible 26464
(175 MiB / 46.2% CPU)
26464
(175 MiB / 46.2% CPU)
HTTP/3 · QUIC HTTP/3 · QUIC 🥇 17829
(128 MiB / 46.1% CPU)
🥇 17829
(128 MiB / 46.1% CPU)
Not possible (no QUIC) Not possible (no QUIC) Not possible Not possible Not possible Not possible 14248
(173 MiB / 48% CPU)
14248
(173 MiB / 48% CPU)

MITM (TWP only)

Same Client×Origin wires with interception on (compare-product 34355136373). Lite = no-op handlers (unchanged-lite finish). Full = append-only header mutation (harness: one probe header each way; product: generic append-only relay via MitmCompressedRelayHelper). nginx/HAProxy/Envoy/YARP cannot MITM. Lite÷Reverse / Full÷Reverse vs bare reverse (same job). Completion gate: Lite ≥ 0.50× and Full ≥ 0.50× reverse sustain @ c=64 (median of 3 GHA runs); reverse TWP÷YARP ≥ 0.75× (no terminate-peer gate).

v1 append-only relay (2026-08-27): Pre-fix H2→H2 Full÷Reverse was 0.13–0.16× (32960766249). Post-fix @ df172718: H2 plain→H2 plain Full 0.77–0.79×, H3→H1 Full 0.91–0.93×, all MITM arms ≥ 0.70× on median of 33041445371, 33055267086, 33055272140.

v2 drop-only + non-unique append (2026-08-27): MitmStaticRebuildHelper rebuilds static HPACK/QPACK after 1–4 unique header drops; trailing non-unique appends stay on compressed relay. @ 84b225f7: all MITM arms ≥ 0.70× on GHA median (33087088466, 33087091622, 33105885748 Linux; 33087085235, 33087088466, 33087091622 Windows). H2 plain→H2 plain Full 0.77–0.79× (Win) / 0.78× (Lin).

Client Origin Lite sustain Full sustain Lite÷Reverse Full÷Reverse
HTTP/1 · plain HTTP/1 · plain 36412
(81 MiB / 51.1% CPU)
35739
(81 MiB / 51.2% CPU)
0.99× 0.97×
HTTP/1 · plain HTTP/1 · TLS 31994
(95 MiB / 48.9% CPU)
32275
(95 MiB / 47.7% CPU)
1.01× 1.02×
HTTP/1 · plain HTTP/2 · plain 50326
(126 MiB / 52% CPU)
47367
(114 MiB / 50.4% CPU)
1× 0.94×
HTTP/1 · plain HTTP/2 · TLS 44750
(123 MiB / 50.3% CPU)
41889
(119 MiB / 48.5% CPU)
1× 0.93×
HTTP/1 · plain HTTP/3 · QUIC 23608
(108 MiB / 52.6% CPU)
22100
(115 MiB / 52.5% CPU)
1.03× 0.97×
HTTP/1 · TLS HTTP/1 · plain 32024
(95 MiB / 47.8% CPU)
31682
(95 MiB / 46.4% CPU)
0.99× 0.98×
HTTP/1 · TLS HTTP/1 · TLS 30200
(95 MiB / 48.4% CPU)
29133
(96 MiB / 47.2% CPU)
0.99× 0.96×
HTTP/1 · TLS HTTP/2 · plain 42118
(126 MiB / 48.8% CPU)
39595
(122 MiB / 49.2% CPU)
0.99× 0.93×
HTTP/1 · TLS HTTP/2 · TLS 38108
(124 MiB / 47.5% CPU)
36366
(127 MiB / 48.1% CPU)
0.99× 0.94×
HTTP/1 · TLS HTTP/3 · QUIC 20258
(116 MiB / 50.9% CPU)
20342
(126 MiB / 51.3% CPU)
0.97× 0.98×
HTTP/2 · plain HTTP/1 · plain 43937
(98 MiB / 52.4% CPU)
44138
(102 MiB / 51.7% CPU)
0.94× 0.95×
HTTP/2 · plain HTTP/1 · TLS 39591
(106 MiB / 50% CPU)
39439
(106 MiB / 52.6% CPU)
0.96× 0.96×
HTTP/2 · plain HTTP/2 · plain 95038
(71 MiB / 37.2% CPU)
93716
(71 MiB / 40.9% CPU)
0.8× 0.79×
HTTP/2 · plain HTTP/2 · TLS 84653
(75 MiB / 37% CPU)
81641
(79 MiB / 38% CPU)
0.83× 0.8×
HTTP/2 · plain HTTP/3 · QUIC 34076
(132 MiB / 52.2% CPU)
33345
(134 MiB / 54.4% CPU)
1.01× 0.99×
HTTP/2 · TLS HTTP/1 · plain 42573
(110 MiB / 54.6% CPU)
41705
(111 MiB / 53.6% CPU)
0.94× 0.92×
HTTP/2 · TLS HTTP/1 · TLS 38002
(105 MiB / 51.8% CPU)
37626
(104 MiB / 52.9% CPU)
0.95× 0.94×
HTTP/2 · TLS HTTP/2 · plain 91812
(86 MiB / 39.7% CPU)
87683
(87 MiB / 39.9% CPU)
0.84× 0.8×
HTTP/2 · TLS HTTP/2 · TLS 82708
(84 MiB / 39% CPU)
78347
(82 MiB / 40.1% CPU)
0.85× 0.81×
HTTP/2 · TLS HTTP/3 · QUIC 32768
(138 MiB / 52.9% CPU)
32140
(143 MiB / 55% CPU)
0.99× 0.97×
HTTP/3 · QUIC HTTP/1 · plain 21240
(112 MiB / 42.3% CPU)
20752
(111 MiB / 43.4% CPU)
0.93× 0.91×
HTTP/3 · QUIC HTTP/1 · TLS 18702
(126 MiB / 42.5% CPU)
19024
(116 MiB / 43.9% CPU)
0.94× 0.96×
HTTP/3 · QUIC HTTP/2 · plain 34449
(146 MiB / 49.9% CPU)
33000
(144 MiB / 49.1% CPU)
0.9× 0.86×
HTTP/3 · QUIC HTTP/2 · TLS 32315
(150 MiB / 47.2% CPU)
30635
(145 MiB / 48.3% CPU)
0.91× 0.86×
HTTP/3 · QUIC HTTP/3 · QUIC 17481
(122 MiB / 49.4% CPU)
16604
(122 MiB / 48% CPU)
0.98× 0.93×

Linux — Titanium vs nginx vs HAProxy vs Envoy vs YARP

Reverse

Median of 3 repeats on ubuntu-latest (4 vCPU / 16 GiB). Bare reverse 5×5 @ 84b225f7 — compare-product 34355136373. Warmup 2s / measure 8s; concurrency 8, 16, 32, 64. Linux nginx is the authoritative nginx baseline. HAProxy (3.2 USE_QUIC) and Envoy (GitHub release, HTTP/3 compiled in) run on the same loopback shape as nginx/YARP. nginx terminate peers use keepalive 256 + streaming buffers. The RPS workflow installs nginx.org mainline (http_v3_module), a QUIC-enabled HAProxy, Envoy, and libmsquic. Prefer ratios over absolute RPS.

Linux reverse

Client Origin TWP sustain TWP peak nginx sustain nginx peak HAProxy sustain HAProxy peak Envoy sustain Envoy peak YARP sustain YARP peak
HTTP/1 · plain HTTP/1 · plain 32568
(88 MiB / 50.9% CPU)
32568
(88 MiB / 50.9% CPU)
🥇 38686
(74 MiB / 41% CPU)
🥇 38686
(74 MiB / 41% CPU)
Not measured Not measured Not measured Not measured 27488
(114 MiB / 50.2% CPU)
27488
(114 MiB / 50.2% CPU)
HTTP/1 · plain HTTP/1 · TLS 24138
(105 MiB / 50.8% CPU)
24138
(105 MiB / 50.8% CPU)
🥇 28999
(89 MiB / 42.3% CPU)
🥇 28999
(89 MiB / 42.3% CPU)
Not measured Not measured Not measured Not measured 21712
(128 MiB / 50.2% CPU)
21712
(128 MiB / 50.2% CPU)
HTTP/1 · plain HTTP/2 · plain 🥇 40674
(126 MiB / 50.8% CPU)
🥇 40674
(126 MiB / 50.8% CPU)
Not possible Not possible Not possible Not possible Not possible Not possible 35672
(123 MiB / 48.8% CPU)
35672
(123 MiB / 48.8% CPU)
HTTP/1 · plain HTTP/2 · TLS 🥇 32698
(146 MiB / 49.6% CPU)
🥇 32698
(146 MiB / 49.6% CPU)
Not possible Not possible Not possible Not possible Not possible Not possible 29688
(132 MiB / 48% CPU)
29688
(132 MiB / 48% CPU)
HTTP/1 · plain HTTP/3 · QUIC 🥇 23815
(137 MiB / 53% CPU)
🥇 23815
(137 MiB / 53% CPU)
Not possible (no QUIC) Not possible (no QUIC) Not possible (no QUIC) Not possible (no QUIC) Not possible (no QUIC) Not possible (no QUIC) 21424
(154 MiB / 49% CPU)
21424
(154 MiB / 49% CPU)
HTTP/1 · TLS HTTP/1 · plain 23109
(108 MiB / 49.8% CPU)
23109
(108 MiB / 49.8% CPU)
🥇 27682
(98 MiB / 41.7% CPU)
🥇 27682
(98 MiB / 41.7% CPU)
Not measured Not measured Not measured Not measured 20355
(136 MiB / 50.6% CPU)
20355
(136 MiB / 50.6% CPU)
HTTP/1 · TLS HTTP/1 · TLS 19602
(110 MiB / 48.8% CPU)
19602
(110 MiB / 48.8% CPU)
🥇 21858
(101 MiB / 41.7% CPU)
🥇 21858
(101 MiB / 41.7% CPU)
Not measured Not measured Not measured Not measured 17177
(135 MiB / 50% CPU)
17177
(135 MiB / 50% CPU)
HTTP/1 · TLS HTTP/2 · plain 🥇 27923
(140 MiB / 50.1% CPU)
🥇 27923
(140 MiB / 50.1% CPU)
Not possible Not possible Not possible Not possible Not possible Not possible 24884
(153 MiB / 48.9% CPU)
24884
(153 MiB / 48.9% CPU)
HTTP/1 · TLS HTTP/2 · TLS 🥇 24310
(155 MiB / 48.8% CPU)
🥇 24310
(155 MiB / 48.8% CPU)
Not possible Not possible Not possible Not possible Not possible Not possible 22345
(144 MiB / 48.4% CPU)
22345
(144 MiB / 48.4% CPU)
HTTP/1 · TLS HTTP/3 · QUIC 🥇 18410
(147 MiB / 51.9% CPU)
🥇 18410
(147 MiB / 51.9% CPU)
Not possible (no QUIC) Not possible (no QUIC) Not possible (no QUIC) Not possible (no QUIC) Not possible (no QUIC) Not possible (no QUIC) 16630
(162 MiB / 49.9% CPU)
16630
(162 MiB / 49.9% CPU)
HTTP/2 · plain HTTP/1 · plain 🥇 37410
(114 MiB / 53.7% CPU)
🥇 37410
(114 MiB / 53.7% CPU)
Not possible Not possible Not possible Not possible Not possible Not possible 34493
(115 MiB / 50% CPU)
34493
(115 MiB / 50% CPU)
HTTP/2 · plain HTTP/1 · TLS 🥇 27674
(122 MiB / 51.8% CPU)
🥇 27674
(122 MiB / 51.8% CPU)
Not possible Not possible Not possible Not possible Not possible Not possible 25774
(126 MiB / 50.6% CPU)
25774
(126 MiB / 50.6% CPU)
HTTP/2 · plain HTTP/2 · plain 🥇 87869
(90 MiB / 36.8% CPU)
🥇 87869
(90 MiB / 36.8% CPU)
Not possible Not possible Not possible Not possible Not possible Not possible 48686
(120 MiB / 46.9% CPU)
48686
(120 MiB / 46.9% CPU)
HTTP/2 · plain HTTP/2 · TLS 🥇 59128
(88 MiB / 36% CPU)
🥇 59128
(88 MiB / 36% CPU)
Not possible Not possible Not possible Not possible Not possible Not possible 39662
(127 MiB / 46.1% CPU)
39662
(127 MiB / 46.1% CPU)
HTTP/2 · plain HTTP/3 · QUIC 🥇 28184
(138 MiB / 50.6% CPU)
🥇 28184
(138 MiB / 50.6% CPU)
Not possible (no QUIC) Not possible (no QUIC) Not possible (no QUIC) Not possible (no QUIC) Not possible (no QUIC) Not possible (no QUIC) 26445
(151 MiB / 47.3% CPU)
26445
(151 MiB / 47.3% CPU)
HTTP/2 · TLS HTTP/1 · plain 🥇 34358
(118 MiB / 52.9% CPU)
🥇 34358
(118 MiB / 52.9% CPU)
15242
(98 MiB / 19.5% CPU)
15242
(98 MiB / 19.5% CPU)
Not measured Not measured Not measured Not measured 28852
(126 MiB / 50% CPU)
28852
(126 MiB / 50% CPU)
HTTP/2 · TLS HTTP/1 · TLS 🥇 26643
(121 MiB / 51.1% CPU)
🥇 26643
(121 MiB / 51.1% CPU)
12678
(108 MiB / 20% CPU)
12678
(108 MiB / 20% CPU)
Not measured Not measured Not measured Not measured 23146
(127 MiB / 50.1% CPU)
23146
(127 MiB / 50.1% CPU)
HTTP/2 · TLS HTTP/2 · plain 🥇 75907
(104 MiB / 36.5% CPU)
🥇 75907
(104 MiB / 36.5% CPU)
Not possible Not possible Not possible Not possible Not possible Not possible 39480
(127 MiB / 46.8% CPU)
39480
(127 MiB / 46.8% CPU)
HTTP/2 · TLS HTTP/2 · TLS 🥇 55030
(97 MiB / 35.6% CPU)
🥇 55030
(97 MiB / 35.6% CPU)
Not possible Not possible Not possible Not possible Not possible Not possible 33713
(129 MiB / 45.6% CPU)
33713
(129 MiB / 45.6% CPU)
HTTP/2 · TLS HTTP/3 · QUIC 🥇 26360
(151 MiB / 49.8% CPU)
🥇 26360
(151 MiB / 49.8% CPU)
Not possible (no QUIC) Not possible (no QUIC) Not possible (no QUIC) Not possible (no QUIC) Not possible (no QUIC) Not possible (no QUIC) 22661
(154 MiB / 47.2% CPU)
22661
(154 MiB / 47.2% CPU)
HTTP/3 · QUIC HTTP/1 · plain 🥇 20591
(145 MiB / 50.3% CPU)
🥇 20591
(145 MiB / 50.3% CPU)
0
(105 MiB / 22.5% CPU)
15091
(105 MiB / 22.5% CPU)
Not measured Not measured Not measured Not measured 18663
(182 MiB / 50.6% CPU)
18663
(182 MiB / 50.6% CPU)
HTTP/3 · QUIC HTTP/1 · TLS 🥇 16216
(158 MiB / 48% CPU)
🥇 16216
(158 MiB / 48% CPU)
0
(113 MiB / 23.2% CPU)
11616
(113 MiB / 23.2% CPU)
Not measured Not measured Not measured Not measured 15332
(194 MiB / 51.1% CPU)
15332
(194 MiB / 51.1% CPU)
HTTP/3 · QUIC HTTP/2 · plain 🥇 29330
(154 MiB / 53.2% CPU)
🥇 29330
(154 MiB / 53.2% CPU)
Not possible (no H3 to H2) Not possible (no H3 to H2) Not possible (no H3 to H2) Not possible (no H3 to H2) Not possible (no H3 to H2) Not possible (no H3 to H2) 24196
(194 MiB / 48.8% CPU)
24196
(194 MiB / 48.8% CPU)
HTTP/3 · QUIC HTTP/2 · TLS 🥇 25134
(156 MiB / 50.3% CPU)
🥇 25134
(156 MiB / 50.3% CPU)
Not possible (no H3 to H2) Not possible (no H3 to H2) Not possible (no H3 to H2) Not possible (no H3 to H2) Not possible (no H3 to H2) Not possible (no H3 to H2) 21501
(192 MiB / 47.6% CPU)
21501
(192 MiB / 47.6% CPU)
HTTP/3 · QUIC HTTP/3 · QUIC 🥇 20462
(151 MiB / 47.2% CPU)
🥇 20462
(151 MiB / 47.2% CPU)
Not possible (no QUIC) Not possible (no QUIC) Not possible (no QUIC) Not possible (no QUIC) Not possible (no QUIC) Not possible (no QUIC) 15995
(200 MiB / 48.2% CPU)
15995
(200 MiB / 48.2% CPU)

MITM (TWP only)

Same Client×Origin wires with interception on (compare-product 34355136373). Lite = no-op handlers (unchanged-lite finish). Full = append-only header mutation (harness: one probe header each way; product: generic append-only relay via MitmCompressedRelayHelper). nginx/HAProxy/Envoy/YARP cannot MITM. Lite÷Reverse / Full÷Reverse vs bare reverse (same job). Completion gate: Lite ≥ 0.50× and Full ≥ 0.50× reverse sustain @ c=64 (median of 3 GHA runs); reverse TWP÷YARP ≥ 0.75× (no terminate-peer gate).

v1 append-only relay (2026-08-27): Pre-fix H2→H2 Full÷Reverse was 0.13–0.16× (32960766249). Post-fix @ df172718: H2 plain→H2 plain Full 0.77–0.79×, H3→H1 Full 0.91–0.93×, all MITM arms ≥ 0.70× on median of 33041445371, 33055267086, 33055272140.

v2 drop-only + non-unique append (2026-08-27): MitmStaticRebuildHelper rebuilds static HPACK/QPACK after 1–4 unique header drops; trailing non-unique appends stay on compressed relay. @ 84b225f7: all MITM arms ≥ 0.70× on GHA median (33087088466, 33087091622, 33105885748 Linux; 33087085235, 33087088466, 33087091622 Windows). H2 plain→H2 plain Full 0.77–0.79× (Win) / 0.78× (Lin).

Client Origin Lite sustain Full sustain Lite÷Reverse Full÷Reverse
HTTP/1 · plain HTTP/1 · plain 31069
(86 MiB / 50.9% CPU)
29689
(86 MiB / 50.4% CPU)
0.95× 0.91×
HTTP/1 · plain HTTP/1 · TLS 23954
(110 MiB / 51.7% CPU)
23460
(111 MiB / 51.1% CPU)
0.99× 0.97×
HTTP/1 · plain HTTP/2 · plain 40096
(129 MiB / 52.2% CPU)
38521
(125 MiB / 53.2% CPU)
0.99× 0.95×
HTTP/1 · plain HTTP/2 · TLS 31783
(142 MiB / 49.4% CPU)
31668
(146 MiB / 50.8% CPU)
0.97× 0.97×
HTTP/1 · plain HTTP/3 · QUIC 23694
(141 MiB / 53.9% CPU)
23040
(132 MiB / 54.5% CPU)
0.99× 0.97×
HTTP/1 · TLS HTTP/1 · plain 23066
(115 MiB / 51.1% CPU)
22531
(114 MiB / 50.8% CPU)
1× 0.97×
HTTP/1 · TLS HTTP/1 · TLS 19653
(115 MiB / 49.4% CPU)
19272
(115 MiB / 49.8% CPU)
1× 0.98×
HTTP/1 · TLS HTTP/2 · plain 27120
(144 MiB / 50.6% CPU)
26816
(145 MiB / 50.9% CPU)
0.97× 0.96×
HTTP/1 · TLS HTTP/2 · TLS 23832
(172 MiB / 48.8% CPU)
23310
(166 MiB / 49% CPU)
0.98× 0.96×
HTTP/1 · TLS HTTP/3 · QUIC 18298
(154 MiB / 53.6% CPU)
17484
(145 MiB / 53.4% CPU)
0.99× 0.95×
HTTP/2 · plain HTTP/1 · plain 35680
(120 MiB / 55.3% CPU)
34876
(118 MiB / 54.6% CPU)
0.95× 0.93×
HTTP/2 · plain HTTP/1 · TLS 27481
(121 MiB / 52.8% CPU)
26631
(122 MiB / 52.4% CPU)
0.99× 0.96×
HTTP/2 · plain HTTP/2 · plain 63316
(94 MiB / 41.1% CPU)
59610
(95 MiB / 41.3% CPU)
0.72× 0.68×
HTTP/2 · plain HTTP/2 · TLS 49517
(102 MiB / 40.3% CPU)
47032
(103 MiB / 39.3% CPU)
0.84× 0.8×
HTTP/2 · plain HTTP/3 · QUIC 28010
(143 MiB / 51.2% CPU)
27712
(154 MiB / 51% CPU)
0.99× 0.98×
HTTP/2 · TLS HTTP/1 · plain 32976
(118 MiB / 53.2% CPU)
33281
(127 MiB / 53.9% CPU)
0.96× 0.97×
HTTP/2 · TLS HTTP/1 · TLS 26015
(121 MiB / 51.4% CPU)
25321
(123 MiB / 51.8% CPU)
0.98× 0.95×
HTTP/2 · TLS HTTP/2 · plain 57548
(109 MiB / 41.5% CPU)
53846
(110 MiB / 40.8% CPU)
0.76× 0.71×
HTTP/2 · TLS HTTP/2 · TLS 46295
(106 MiB / 39.7% CPU)
43960
(112 MiB / 39.9% CPU)
0.84× 0.8×
HTTP/2 · TLS HTTP/3 · QUIC 26239
(147 MiB / 50.6% CPU)
26112
(151 MiB / 51.2% CPU)
1× 0.99×
HTTP/3 · QUIC HTTP/1 · plain 19986
(142 MiB / 51.2% CPU)
19542
(145 MiB / 50.4% CPU)
0.97× 0.95×
HTTP/3 · QUIC HTTP/1 · TLS 16039
(164 MiB / 48.7% CPU)
15437
(148 MiB / 48.7% CPU)
0.99× 0.95×
HTTP/3 · QUIC HTTP/2 · plain 28091
(161 MiB / 55% CPU)
27167
(162 MiB / 54.1% CPU)
0.96× 0.93×
HTTP/3 · QUIC HTTP/2 · TLS 23686
(158 MiB / 51.8% CPU)
22948
(163 MiB / 51.6% CPU)
0.94× 0.91×
HTTP/3 · QUIC HTTP/3 · QUIC 19537
(156 MiB / 49.6% CPU)
19191
(153 MiB / 50% CPU)
0.95× 0.94×

macOS — Titanium vs nginx vs HAProxy vs Envoy vs YARP

Numbers are filled by tools/RpsLoadProbe/apply-wiki-paste.ps1 after compare-product on macos-15-intel (placeholder headers match the Linux table shape).

Reverse

Median of 3 repeats on macos-15-intel (4-core / 14 GB). Bare reverse 5×5 @ 84b225f7 — compare-product 34355136373. Warmup 2s / measure 8s; concurrency 8, 16, 32, 64. Prefer TWP÷peer ratios over absolute RPS. RPS cells include median RSS / CPU at the peak-RPS step as <br><sub>(MiB / CPU%)</sub>. The RPS workflow installs Homebrew nginx (http_v3_module), Homebrew libmsquic (+ DYLD_*), and YARP. HAProxy/Envoy are not published on macOS. Do not publish from macos-latest (3-core / 7 GB).

macOS reverse

Client Origin TWP sustain TWP peak nginx sustain nginx peak HAProxy sustain HAProxy peak Envoy sustain Envoy peak YARP sustain YARP peak
HTTP/1 · plain HTTP/1 · plain 🥇 14478
(83 MiB / 30.4% CPU)
🥇 14478
(83 MiB / 30.4% CPU)
10550
(50 MiB / 16.7% CPU)
10550
(50 MiB / 16.7% CPU)
Not measured Not measured Not measured Not measured 12414
(103 MiB / 36.5% CPU)
12414
(103 MiB / 36.5% CPU)
HTTP/1 · plain HTTP/1 · TLS 🥇 13700
(93 MiB / 35.7% CPU)
🥇 13700
(93 MiB / 35.7% CPU)
6014
(71 MiB / 14.9% CPU)
6014
(71 MiB / 14.9% CPU)
Not measured Not measured Not measured Not measured 11438
(124 MiB / 36% CPU)
11438
(124 MiB / 36% CPU)
HTTP/1 · plain HTTP/2 · plain 13919
(90 MiB / 35.7% CPU)
13919
(90 MiB / 35.7% CPU)
Not possible Not possible Not possible Not possible Not possible Not possible 🥇 16104
(112 MiB / 31.9% CPU)
🥇 16104
(112 MiB / 31.9% CPU)
HTTP/1 · plain HTTP/2 · TLS 11700
(128 MiB / 35.6% CPU)
11700
(128 MiB / 35.6% CPU)
Not possible Not possible Not possible Not possible Not possible Not possible 🥇 13487
(115 MiB / 32.1% CPU)
🥇 13487
(115 MiB / 32.1% CPU)
HTTP/1 · plain HTTP/3 · QUIC 4115
(89 MiB / 37.8% CPU)
4115
(89 MiB / 37.8% CPU)
Not possible (no QUIC) Not possible (no QUIC) Not possible (no QUIC) Not possible (no QUIC) Not possible (no QUIC) Not possible (no QUIC) 🥇 5811
(114 MiB / 35.8% CPU)
🥇 5811
(114 MiB / 35.8% CPU)
HTTP/1 · TLS HTTP/1 · plain 7678
(94 MiB / 29.8% CPU)
7678
(94 MiB / 29.8% CPU)
5877
(71 MiB / 14.1% CPU)
5877
(71 MiB / 14.1% CPU)
Not measured Not measured Not measured Not measured 🥇 11015
(117 MiB / 35% CPU)
🥇 11015
(117 MiB / 35% CPU)
HTTP/1 · TLS HTTP/1 · TLS 🥇 9657
(98 MiB / 33.5% CPU)
🥇 9657
(98 MiB / 33.5% CPU)
6106
(80 MiB / 20% CPU)
6106
(80 MiB / 20% CPU)
Not measured Not measured Not measured Not measured 9081
(119 MiB / 33.3% CPU)
9081
(119 MiB / 33.3% CPU)
HTTP/1 · TLS HTTP/2 · plain 🥇 13563
(96 MiB / 35.6% CPU)
🥇 13563
(96 MiB / 35.6% CPU)
Not possible Not possible Not possible Not possible Not possible Not possible 13036
(115 MiB / 30.8% CPU)
13036
(115 MiB / 30.8% CPU)
HTTP/1 · TLS HTTP/2 · TLS 10792
(166 MiB / 33% CPU)
10792
(166 MiB / 33% CPU)
Not possible Not possible Not possible Not possible Not possible Not possible 🥇 12303
(118 MiB / 29.9% CPU)
🥇 12303
(118 MiB / 29.9% CPU)
HTTP/1 · TLS HTTP/3 · QUIC 3572
(106 MiB / 47.1% CPU)
3572
(106 MiB / 47.1% CPU)
Not possible (no QUIC) Not possible (no QUIC) Not possible (no QUIC) Not possible (no QUIC) Not possible (no QUIC) Not possible (no QUIC) 🥇 5128
(119 MiB / 32.2% CPU)
🥇 5128
(119 MiB / 32.2% CPU)
HTTP/2 · plain HTTP/1 · plain 🥇 19995
(88 MiB / 36.9% CPU)
🥇 19995
(88 MiB / 36.9% CPU)
Not possible Not possible Not possible Not possible Not possible Not possible 18482
(104 MiB / 38.6% CPU)
18482
(104 MiB / 38.6% CPU)
HTTP/2 · plain HTTP/1 · TLS 🥇 16104
(96 MiB / 39.9% CPU)
🥇 16104
(96 MiB / 39.9% CPU)
Not possible Not possible Not possible Not possible Not possible Not possible 14089
(118 MiB / 39.5% CPU)
14089
(118 MiB / 39.5% CPU)
HTTP/2 · plain HTTP/2 · plain 🥇 41873
(73 MiB / 28.5% CPU)
🥇 41873
(73 MiB / 28.5% CPU)
Not possible Not possible Not possible Not possible Not possible Not possible 27049
(111 MiB / 34.2% CPU)
27049
(111 MiB / 34.2% CPU)
HTTP/2 · plain HTTP/2 · TLS 🥇 33498
(76 MiB / 26.8% CPU)
🥇 33498
(76 MiB / 26.8% CPU)
Not possible Not possible Not possible Not possible Not possible Not possible 17026
(112 MiB / 35.1% CPU)
17026
(112 MiB / 35.1% CPU)
HTTP/2 · plain HTTP/3 · QUIC 4511
(93 MiB / 43.8% CPU)
4511
(93 MiB / 43.8% CPU)
Not possible (no QUIC) Not possible (no QUIC) Not possible (no QUIC) Not possible (no QUIC) Not possible (no QUIC) Not possible (no QUIC) 🥇 5514
(117 MiB / 34.1% CPU)
🥇 5514
(117 MiB / 34.1% CPU)
HTTP/2 · TLS HTTP/1 · plain 🥇 14218
(93 MiB / 39.1% CPU)
🥇 14218
(93 MiB / 39.1% CPU)
9156
(71 MiB / 13.5% CPU)
9156
(71 MiB / 13.5% CPU)
Not measured Not measured Not measured Not measured 12587
(112 MiB / 35.7% CPU)
12587
(112 MiB / 35.7% CPU)
HTTP/2 · TLS HTTP/1 · TLS 🥇 12930
(97 MiB / 40.2% CPU)
🥇 12930
(97 MiB / 40.2% CPU)
7276
(91 MiB / 14% CPU)
7276
(91 MiB / 14% CPU)
Not measured Not measured Not measured Not measured 9599
(126 MiB / 37.7% CPU)
9599
(126 MiB / 37.7% CPU)
HTTP/2 · TLS HTTP/2 · plain 🥇 27986
(83 MiB / 25.4% CPU)
🥇 27986
(83 MiB / 25.4% CPU)
Not possible Not possible Not possible Not possible Not possible Not possible 17430
(117 MiB / 34.7% CPU)
17430
(117 MiB / 34.7% CPU)
HTTP/2 · TLS HTTP/2 · TLS 🥇 27877
(84 MiB / 28% CPU)
🥇 27877
(84 MiB / 28% CPU)
Not possible Not possible Not possible Not possible Not possible Not possible 17531
(116 MiB / 35.2% CPU)
17531
(116 MiB / 35.2% CPU)
HTTP/2 · TLS HTTP/3 · QUIC 4398
(106 MiB / 37.2% CPU)
4398
(106 MiB / 37.2% CPU)
Not possible (no QUIC) Not possible (no QUIC) Not possible (no QUIC) Not possible (no QUIC) Not possible (no QUIC) Not possible (no QUIC) 🥇 4593
(124 MiB / 33.3% CPU)
🥇 4593
(124 MiB / 33.3% CPU)
HTTP/3 · QUIC HTTP/1 · plain 3758
(100 MiB / 35.5% CPU)
3758
(100 MiB / 35.5% CPU)
0
(60 MiB / 9% CPU)
4865
(60 MiB / 9% CPU)
Not measured Not measured Not measured Not measured 🥇 4861
(185 MiB / 36% CPU)
🥇 4861
(185 MiB / 36% CPU)
HTTP/3 · QUIC HTTP/1 · TLS 2842
(119 MiB / 29.7% CPU)
2842
(119 MiB / 29.7% CPU)
0
(65 MiB / 3.4% CPU)
1572
(65 MiB / 3.4% CPU)
Not measured Not measured Not measured Not measured 🥇 3779
(208 MiB / 33.4% CPU)
🥇 3779
(208 MiB / 33.4% CPU)
HTTP/3 · QUIC HTTP/2 · plain 4523
(96 MiB / 35.4% CPU)
4523
(96 MiB / 35.4% CPU)
Not possible (no H3 to H2) Not possible (no H3 to H2) Not possible (no H3 to H2) Not possible (no H3 to H2) Not possible (no H3 to H2) Not possible (no H3 to H2) 🥇 5580
(176 MiB / 31.1% CPU)
🥇 5580
(176 MiB / 31.1% CPU)
HTTP/3 · QUIC HTTP/2 · TLS 3932
(106 MiB / 34.6% CPU)
3932
(106 MiB / 34.6% CPU)
Not possible (no H3 to H2) Not possible (no H3 to H2) Not possible (no H3 to H2) Not possible (no H3 to H2) Not possible (no H3 to H2) Not possible (no H3 to H2) 🥇 5020
(185 MiB / 32.1% CPU)
🥇 5020
(185 MiB / 32.1% CPU)
HTTP/3 · QUIC HTTP/3 · QUIC 3057
(99 MiB / 39.9% CPU)
3057
(99 MiB / 39.9% CPU)
Not possible (no QUIC) Not possible (no QUIC) Not possible (no QUIC) Not possible (no QUIC) Not possible (no QUIC) Not possible (no QUIC) 🥇 3612
(180 MiB / 32.4% CPU)
🥇 3612
(180 MiB / 32.4% CPU)

MITM (TWP only)

Same Client×Origin wires with interception on (compare-product 34355136373). Lite = no-op handlers (unchanged-lite finish). Full = append-only header mutation (harness: one probe header each way; product: generic append-only relay via MitmCompressedRelayHelper). nginx/HAProxy/Envoy/YARP cannot MITM. Lite÷Reverse / Full÷Reverse vs bare reverse (same job). Completion gate: Lite ≥ 0.50× and Full ≥ 0.50× reverse sustain @ c=64 (median of 3 GHA runs); reverse TWP÷YARP ≥ 0.75× (no terminate-peer gate).

v1 append-only relay (2026-08-27): Pre-fix H2→H2 Full÷Reverse was 0.13–0.16× (32960766249). Post-fix @ df172718: H2 plain→H2 plain Full 0.77–0.79×, H3→H1 Full 0.91–0.93×, all MITM arms ≥ 0.70× on median of 33041445371, 33055267086, 33055272140.

v2 drop-only + non-unique append (2026-08-27): MitmStaticRebuildHelper rebuilds static HPACK/QPACK after 1–4 unique header drops; trailing non-unique appends stay on compressed relay. @ 84b225f7: all MITM arms ≥ 0.70× on GHA median (33087088466, 33087091622, 33105885748 Linux; 33087085235, 33087088466, 33087091622 Windows). H2 plain→H2 plain Full 0.77–0.79× (Win) / 0.78× (Lin).

Client Origin Lite sustain Full sustain Lite÷Reverse Full÷Reverse
HTTP/1 · plain HTTP/1 · plain 11551
(84 MiB / 33.4% CPU)
10010
(84 MiB / 30.5% CPU)
0.8× 0.69×
HTTP/1 · plain HTTP/1 · TLS 7962
(93 MiB / 33.8% CPU)
8731
(95 MiB / 35.2% CPU)
0.58× 0.64×
HTTP/1 · plain HTTP/2 · plain 12047
(89 MiB / 35.7% CPU)
11489
(90 MiB / 35.6% CPU)
0.87× 0.83×
HTTP/1 · plain HTTP/2 · TLS 9733
(103 MiB / 35% CPU)
9788
(113 MiB / 34.8% CPU)
0.83× 0.84×
HTTP/1 · plain HTTP/3 · QUIC 3650
(90 MiB / 39.2% CPU)
3366
(90 MiB / 41% CPU)
0.89× 0.82×
HTTP/1 · TLS HTTP/1 · plain 6968
(95 MiB / 31.4% CPU)
6771
(94 MiB / 31.8% CPU)
0.91× 0.88×
HTTP/1 · TLS HTTP/1 · TLS 6188
(98 MiB / 30.9% CPU)
5395
(98 MiB / 31.4% CPU)
0.64× 0.56×
HTTP/1 · TLS HTTP/2 · plain 10565
(100 MiB / 34.4% CPU)
7140
(123 MiB / 31.8% CPU)
0.78× 0.53×
HTTP/1 · TLS HTTP/2 · TLS 8064
(142 MiB / 33.3% CPU)
6232
(149 MiB / 30.9% CPU)
0.75× 0.58×
HTTP/1 · TLS HTTP/3 · QUIC 3399
(103 MiB / 44.7% CPU)
2361
(103 MiB / 42.2% CPU)
0.95× 0.66×
HTTP/2 · plain HTTP/1 · plain 18121
(90 MiB / 36.8% CPU)
12319
(90 MiB / 36.1% CPU)
0.91× 0.62×
HTTP/2 · plain HTTP/1 · TLS 11021
(97 MiB / 39.1% CPU)
10356
(97 MiB / 39.1% CPU)
0.68× 0.64×
HTTP/2 · plain HTTP/2 · plain 23305
(75 MiB / 27.9% CPU)
26794
(76 MiB / 28.6% CPU)
0.56× 0.64×
HTTP/2 · plain HTTP/2 · TLS 23391
(81 MiB / 30.9% CPU)
20102
(80 MiB / 31.1% CPU)
0.7× 0.6×
HTTP/2 · plain HTTP/3 · QUIC 4355
(94 MiB / 45.1% CPU)
3978
(94 MiB / 44.1% CPU)
0.97× 0.88×
HTTP/2 · TLS HTTP/1 · plain 13960
(93 MiB / 40.6% CPU)
11494
(93 MiB / 38.2% CPU)
0.98× 0.81×
HTTP/2 · TLS HTTP/1 · TLS 10042
(95 MiB / 38.5% CPU)
9315
(99 MiB / 38.4% CPU)
0.78× 0.72×
HTTP/2 · TLS HTTP/2 · plain 26966
(87 MiB / 29.9% CPU)
24551
(88 MiB / 31.6% CPU)
0.96× 0.88×
HTTP/2 · TLS HTTP/2 · TLS 23643
(86 MiB / 31.3% CPU)
20835
(87 MiB / 30.8% CPU)
0.85× 0.75×
HTTP/2 · TLS HTTP/3 · QUIC 4434
(109 MiB / 40.3% CPU)
4820
(106 MiB / 41.9% CPU)
1.01× 1.1×
HTTP/3 · QUIC HTTP/1 · plain 4184
(101 MiB / 39.5% CPU)
3683
(102 MiB / 37.9% CPU)
1.11× 0.98×
HTTP/3 · QUIC HTTP/1 · TLS 3767
(115 MiB / 38.4% CPU)
3587
(115 MiB / 36.5% CPU)
1.33× 1.26×
HTTP/3 · QUIC HTTP/2 · plain 5009
(98 MiB / 35.9% CPU)
4245
(97 MiB / 39.4% CPU)
1.11× 0.94×
HTTP/3 · QUIC HTTP/2 · TLS 4944
(101 MiB / 39.7% CPU)
3887
(106 MiB / 40.4% CPU)
1.26× 0.99×
HTTP/3 · QUIC HTTP/3 · QUIC 3059
(93 MiB / 35.6% CPU)
2631
(94 MiB / 35.6% CPU)
1× 0.86×

Editions (CLI / Plus / Intercept)

Note: twp-reverse-http1 and other library rows use Core with probe-tuned settings (no logging, no Via header, probe-warmed certs). Edition rows use titanium run -c twp.yaml product defaults — prefer the ÷baseline ratio column over absolute RPS. Inspector GUI is not spawnable in the harness; session-path overhead is twp-cli-intercept-http1 (route RequestHeaderSet transform).

Gates prioritize SLO survival under load and reasonable overhead vs baseline. Pre-origin Plus middleware (CIDR/WAF/JWT/rate-limit) runs on H1 terminate-lite without SessionEventArgs; JWT caches successful bearer validations. Thresholds — see PERF-GATES.md.

Median of 3 repeats @ 6d2a7c9d. Source: Actions 33259699099 (compare-editions). Warmup 2s / measure 8s; concurrency 8–64; sustain = median peak RPS among SLO-pass steps @ c=64. RPS cells include (MiB / CPU%) at that step.

pwsh tools/RpsLoadProbe/run-rps.ps1 -Mode compare-editions
pwsh tools/RpsLoadProbe/validate-edition-gates.ps1 -CsvPath tools/RpsLoadProbe/results/rps-ramp-*.csv
Arm Win sustain Win peak Linux sustain Linux peak Win÷ Lin÷ Gate
twp-cli-reverse-http1 vs library 32214
(118 MiB / 43.4% CPU)
32219 47734
(151 MiB / 49.0% CPU)
47844 1.02× 1.04× ≥ 0.80×
twp-cli-reverse-http1-tls vs library TLS 26495
(138 MiB / 48.3% CPU)
26651 36932
(174 MiB / 48.5% CPU)
37200 1.01× 1.00× ≥ 0.80×
twp-cli-reverse-http1-route vs CLI 32394
(120 MiB / 47.7% CPU)
32428 47369
(149 MiB / 49.2% CPU)
47398 1.01× 0.99× ≥ 0.90×
twp-cli-plus-base-http1 vs CLI 32475
(123 MiB / 46.0% CPU)
32695 47322
(154 MiB / 49.8% CPU)
47744 1.01× 0.99× ≥ 0.90×
twp-cli-plus-cache-http1 (cold) vs CLI 34029
(118 MiB / 64.5% CPU)
34460 49409
(148 MiB / 62.3% CPU)
50104 1.06× 1.04× ≥ 0.70×
twp-cli-intercept-http1 vs CLI 25219
(124 MiB / 54.0% CPU)
25377 35969
(155 MiB / 51.2% CPU)
35977 0.78× 0.75× ≥ 0.70×
twp-cli-plus-waf-http1 vs CLI 31653
(124 MiB / 46.8% CPU)
31837 46367
(151 MiB / 49.6% CPU)
46685 0.98× 0.97× ≥ 0.80×
twp-cli-plus-cidr-http1 vs CLI 31730
(123 MiB / 51.1% CPU)
31757 46828
(150 MiB / 50.2% CPU)
47093 0.98× 0.98× ≥ 0.80×
twp-cli-plus-jwt-http1 vs CLI 30326
(138 MiB / 46.6% CPU)
30386 43906
(179 MiB / 49.5% CPU)
44433 0.94× 0.92× ≥ 0.70×
twp-cli-plus-ratelimit-http1 vs CLI 31713
(123 MiB / 48.8% CPU)
31944 46435
(151 MiB / 49.4% CPU)
46860 0.98× 0.97× ≥ 0.80×
twp-cli-plus-resilience-http1 vs CLI 32373
(128 MiB / 50.5% CPU)
32416 47302
(155 MiB / 49.5% CPU)
47654 1.00× 0.99× ≥ 0.85×
twp-cli-plus-discovery-file-http1 vs CLI 32209
(121 MiB / 46.5% CPU)
32332 47485
(151 MiB / 49.1% CPU)
47770 1.00× 0.99× ≥ 0.80×
twp-cli-plus-metrics-scrape-http1 vs CLI 32192
(126 MiB / 44.8% CPU)
32427 47348
(159 MiB / 49.5% CPU)
48665 1.00× 0.99× ≥ 0.80×
twp-cli-plus-cache-hit-http1 vs cache cold 34015
(117 MiB / 66.0% CPU)
34022 49153
(149 MiB / 63.1% CPU)
49358 1.00× 0.99× ≥ 0.90×
twp-cli-static-http1 vs CLI 53078
(109 MiB / 52.1% CPU)
53980 78333
(150 MiB / 48.4% CPU)
82955 1.65× 1.64× ≥ 0.85×
twp-cli-logging-http1 vs CLI 32440
(119 MiB / 47.4% CPU)
32581 47624
(150 MiB / 49.3% CPU)
48604 1.01× 1.00× ≥ 0.90×
twp-cli-lb-leasttime-http1 vs route 29649
(134 MiB / 54.7% CPU)
30386 44790
(180 MiB / 50.6% CPU)
44825 0.92× 0.95× ≥ 0.85×
twp-cli-dialect-twp-http1 vs CLI 32461
(114 MiB / 49.7% CPU)
32780 47898
(145 MiB / 49.3% CPU)
48154 1.01× 1.00× ≥ 0.90×

validate-edition-gates.ps1 passed on both OS for this run. Library baselines @ c=64 (same job): Win H1 31561 / TLS 26128; Linux H1 45818 / TLS 36869. Laptop smoke ratios stay on Performance Local Lab — Editions.

Cross-version (7.0 vs 6.0)

Gate 2 prerequisite before the v7.0.0 tag: run compare-cross-version (reverse matrix, routes unset) on develop and compare against committed 6.0 baselines from GHA 33087088466 (tools/RpsLoadProbe/results/baseline-6.0-win.csv / baseline-6.0-linux.csv).

Gate Threshold
Peer-normalized RPS (when YARP peer exists) (TWP÷YARP)_7 ÷ (TWP÷YARP)_6 ≥ 0.90 or current (TWP÷YARP) ≥ 0.90
Absolute RPS floor (TWP arms) 7.0 ÷ 6.0 ≥ 0.70 (runner heat — peers move with the box)
RSS 7.0 ÷ 6.0 ≤ 1.20 per TWP arm @ c=64

nginx/YARP absolute RPS is not gated. See validate-cross-version.ps1.

pwsh tools/RpsLoadProbe/run-rps.ps1 -Mode compare-cross-version
pwsh tools/RpsLoadProbe/validate-cross-version.ps1 `
  -BaselineCsv tools/RpsLoadProbe/results/baseline-6.0-win.csv `
  -CurrentCsv  tools/RpsLoadProbe/results/rps-ramp-*.csv

Median of 3 @ 0ef6d4dd. Source: Actions 33270571908. Sustain RPS @ c=64 (SLO-pass median). Absolute 7.0÷6.0 ≈ 0.96–1.00× on same-protocol arms; peer-norm ≈ 0.90–1.03×.

Arm Win 6.0 Win 7.0 Win÷ Linux 6.0 Linux 7.0 Lin÷
twp-reverse-http1 32525 32213 0.99× 32755 31932 0.97×
twp-reverse-http1-tls 26461 26570 1.00× 22784 22351 0.98×
twp-reverse-http2 76038 75316 0.99× 47690 46678 0.98×
twp-reverse-http2-cleartext 40347 40236 1.00× 34556 33316 0.96×
twp-reverse-http3 17529 17380 0.99× 19468 19423 1.00×
twp-reverse-http3-cleartext 19956 19529 0.98× 20395 20070 0.98×
yarp-reverse-http1 (peer) 27504 27254 0.99× 27713 27102 0.98×
yarp-reverse-http2 (peer) 35553 35189 0.99× 29111 28733 0.99×

validate-cross-version.ps1 passed on both OS CSVs (RSS floor 1.20; peer-norm also passes when current TWP÷YARP ≥ 0.90). MITM arms live in compare-product / compare-mitm, not this reverse-only matrix.

Heavier reverse workloads

Separate from the tiny-GET matrix. Same measurement environments. Modes: compare-bodies, compare-post, compare-lossy, compare-tls-cost, compare-arch in RpsLoadProbe. Dispatch each independently via workflow_dispatch (no need to re-run full compare-product). Charts: render-heavier-charts.py (rps-heavier-*-{windows,linux}.png). PUT with the same body is the same proxy work as POST; DELETE with no body matches GET — only POST is published. Bodies/POST/lossy stay half-duplex. compare-arch is the slow-consumer / early-response / duplex set. Laptop numbers are on Performance Local Lab; CI medians go in the tables below. Tables below are pre–5-peer publish; re-paste with paste-heavier-wiki.py after the next heavier GHA runs for HAProxy/Envoy columns.

Lossy link = userspace shim (not kernel netem): TCP gets per-buffer delay + occasional whole-connection stalls (honest HOL for multiplexed H2); UDP gets per-datagram delay + drops (QUIC). compare-lossy publishes H1/H2/H3; H3 is where the protocol design is supposed to matter.

Windows — heavier reverse GET (64 KiB / 256 KiB)

Windows heavier bodies

Median of 3 repeats on windows-latest @ 84b225f7. Source: Actions 34355153953 (compare-bodies). Warmup 2s / measure 8s. RPS cells include (MiB / CPU%) footprints.

Body Client Origin TWP sustain TWP peak nginx sustain nginx peak HAProxy sustain HAProxy peak Envoy sustain Envoy peak YARP sustain YARP peak
64 KiB HTTP/1 · TLS HTTP/1 · plain 🥇 12,918
(123 MiB / 45.1% CPU)
12,918
(123 MiB / 45.1% CPU)
897
(140 MiB / 24.9% CPU)
897
(140 MiB / 24.9% CPU)
Not possible Not possible Not possible Not possible 11,239
(133 MiB / 48.6% CPU)
11,239
(133 MiB / 48.6% CPU)
64 KiB HTTP/2 · TLS HTTP/1 · plain 🥇 11,029
(173 MiB / 49.0% CPU)
11,029
(173 MiB / 49.0% CPU)
805
(140 MiB / 24.9% CPU)
805
(140 MiB / 24.9% CPU)
Not possible Not possible Not possible Not possible 9,277
(134 MiB / 49.2% CPU)
9,277
(134 MiB / 49.2% CPU)
64 KiB HTTP/3 · QUIC HTTP/1 · plain 🥇 6,015
(139 MiB / 39.8% CPU)
6,015
(139 MiB / 39.8% CPU)
Not possible (no QUIC) Not possible (no QUIC) Not possible Not possible Not possible Not possible 5,025
(193 MiB / 48.9% CPU)
5,025
(193 MiB / 48.9% CPU)
256 KiB HTTP/1 · TLS HTTP/1 · plain 🥇 3,517
(133 MiB / 40.5% CPU)
3,517
(133 MiB / 40.5% CPU)
238
(141 MiB / 24.9% CPU)
238
(141 MiB / 24.9% CPU)
Not possible Not possible Not possible Not possible 3,351
(130 MiB / 47.9% CPU)
3,351
(130 MiB / 47.9% CPU)
256 KiB HTTP/2 · TLS HTTP/1 · plain 🥇 3,273
(148 MiB / 39.7% CPU)
3,273
(148 MiB / 39.7% CPU)
213
(140 MiB / 24.8% CPU)
213
(140 MiB / 24.8% CPU)
Not possible Not possible Not possible Not possible 2,343
(133 MiB / 42.5% CPU)
2,343
(133 MiB / 42.5% CPU)
256 KiB HTTP/3 · QUIC HTTP/1 · plain 🥇 1,504
(113 MiB / 40.9% CPU)
1,504
(113 MiB / 40.9% CPU)
Not possible (no QUIC) Not possible (no QUIC) Not possible Not possible Not possible Not possible 1,366
(177 MiB / 46.7% CPU)
1,366
(177 MiB / 46.7% CPU)

nginx/Windows collapses on large reverse bodies in this harness; treat as same-OS only. H1 TLS 64 KiB ≈ 1.11× YARP; 256 KiB ≈ 1.23×. H2→H1 64 KiB ≈ 1.21×; H3→H1 64 KiB ≈ 1.18×.

Linux — heavier reverse GET (64 KiB / 256 KiB)

Linux heavier bodies

Median of 3 repeats @ 84b225f7. Source: Actions 34355153953 (compare-bodies). Warmup 2s / measure 8s.

Body Client Origin TWP sustain TWP peak nginx sustain nginx peak HAProxy sustain HAProxy peak Envoy sustain Envoy peak YARP sustain YARP peak
64 KiB HTTP/1 · TLS HTTP/1 · plain 🥇 7,745
(175 MiB / 45.1% CPU)
7,745
(175 MiB / 45.1% CPU)
5,430
(98 MiB / 51.4% CPU)
5,430
(98 MiB / 51.4% CPU)
Not measured Not measured Not measured Not measured 6,321
(164 MiB / 48.6% CPU)
6,321
(164 MiB / 48.6% CPU)
64 KiB HTTP/2 · TLS HTTP/1 · plain 🥇 5,731
(236 MiB / 40.2% CPU)
5,731
(236 MiB / 40.2% CPU)
1,738
(99 MiB / 15.7% CPU)
1,738
(99 MiB / 15.7% CPU)
Not measured Not measured Not measured Not measured 4,888
(162 MiB / 47.8% CPU)
4,888
(162 MiB / 47.8% CPU)
64 KiB HTTP/3 · QUIC HTTP/1 · plain 🥇 5,505
(179 MiB / 43.9% CPU)
5,505
(179 MiB / 43.9% CPU)
0
(126 MiB / 22.2% CPU)
1,686
(126 MiB / 22.2% CPU)
Not measured Not measured Not measured Not measured 4,256
(227 MiB / 51.5% CPU)
4,256
(227 MiB / 51.5% CPU)
256 KiB HTTP/1 · TLS HTTP/1 · plain 🥇 2,670
(122 MiB / 37.4% CPU)
2,670
(122 MiB / 37.4% CPU)
1,712
(98 MiB / 53.1% CPU)
1,712
(98 MiB / 53.1% CPU)
Not measured Not measured Not measured Not measured 2,139
(168 MiB / 45.9% CPU)
2,139
(168 MiB / 45.9% CPU)
256 KiB HTTP/2 · TLS HTTP/1 · plain 🥇 1,498
(192 MiB / 32.1% CPU)
1,498
(192 MiB / 32.1% CPU)
568
(98 MiB / 23.9% CPU)
568
(98 MiB / 23.9% CPU)
Not measured Not measured Not measured Not measured 1,317
(159 MiB / 44.6% CPU)
1,317
(159 MiB / 44.6% CPU)
256 KiB HTTP/3 · QUIC HTTP/1 · plain 🥇 1,442
(152 MiB / 44.2% CPU)
1,442
(152 MiB / 44.2% CPU)
0
(119 MiB / 20.1% CPU)
0
(119 MiB / 20.1% CPU)
Not measured Not measured Not measured Not measured 1,253
(210 MiB / 48.4% CPU)
1,253
(210 MiB / 48.4% CPU)

On this GHA pass TWP÷YARP H1 TLS ≈ 1.23× (64 KiB) / 1.28× (256 KiB); H2→H1 ≈ 1.23× / 1.16×; H3→H1 ≈ 1.27× / 1.13×. TWP÷nginx H1 TLS ≈ 1.43 / 1.58. Absolute RPS swings by VM; prefer ratios.

Windows — POST 64 KiB request + 64 KiB response

Windows heavier POST

Median of 3 repeats on windows-latest @ 84b225f7. Source: Actions 34355158209 (compare-post).

Client Origin TWP sustain TWP peak nginx sustain nginx peak HAProxy sustain HAProxy peak Envoy sustain Envoy peak YARP sustain YARP peak
HTTP/1 · TLS HTTP/1 · plain 🥇 9,481
(108 MiB / 40.6% CPU)
9,481
(108 MiB / 40.6% CPU)
573
(141 MiB / 24.8% CPU)
573
(141 MiB / 24.8% CPU)
Not possible Not possible Not possible Not possible 6,465
(136 MiB / 52.0% CPU)
6,465
(136 MiB / 52.0% CPU)
HTTP/2 · TLS HTTP/1 · plain 🥇 6,817
(180 MiB / 49.4% CPU)
6,817
(180 MiB / 49.4% CPU)
587
(142 MiB / 24.8% CPU)
587
(142 MiB / 24.8% CPU)
Not possible Not possible Not possible Not possible 5,523
(135 MiB / 53.4% CPU)
5,523
(135 MiB / 53.4% CPU)
HTTP/3 · QUIC HTTP/1 · plain 🥇 3,502
(175 MiB / 44.1% CPU)
3,502
(175 MiB / 44.1% CPU)
Not possible (no QUIC) Not possible (no QUIC) Not possible Not possible Not possible Not possible 3,143
(212 MiB / 51.6% CPU)
3,143
(212 MiB / 51.6% CPU)

TWP leads H1 POST (1.5× YARP), H2 POST (1.2× YARP), and H3 POST (~1.1× YARP).

Linux — POST 64 KiB request + 64 KiB response

Linux heavier POST

Median of 3 repeats @ 84b225f7. Source: Actions 34355158209 (compare-post).

Client Origin TWP sustain TWP peak nginx sustain nginx peak HAProxy sustain HAProxy peak Envoy sustain Envoy peak YARP sustain YARP peak
HTTP/1 · TLS HTTP/1 · plain 🥇 4,787
(132 MiB / 45.5% CPU)
4,787
(132 MiB / 45.5% CPU)
3,500
(98 MiB / 47.8% CPU)
3,500
(98 MiB / 47.8% CPU)
Not measured Not measured Not measured Not measured 3,143
(175 MiB / 55.5% CPU)
3,143
(175 MiB / 55.5% CPU)
HTTP/2 · TLS HTTP/1 · plain 🥇 2,829
(210 MiB / 44.6% CPU)
2,829
(210 MiB / 44.6% CPU)
1,431
(113 MiB / 23.1% CPU)
1,431
(113 MiB / 23.1% CPU)
Not measured Not measured Not measured Not measured 2,524
(169 MiB / 47.6% CPU)
2,524
(169 MiB / 47.6% CPU)
HTTP/3 · QUIC HTTP/1 · plain 🥇 2,907
(224 MiB / 44.1% CPU)
2,907
(224 MiB / 44.1% CPU)
457
(108 MiB / 24.8% CPU)
457
(108 MiB / 24.8% CPU)
Not measured Not measured Not measured Not measured 2,642
(248 MiB / 49.6% CPU)
2,642
(248 MiB / 49.6% CPU)

Linux nginx H1/H2/H3 POST completed (nginx.org mainline). TWP÷YARP H1 ≈ 1.5×; H2 ≈ 1.3×; H3 ≈ 1.1×. TWP÷nginx H3 ≈ 4×.

Windows — lossy / high-RTT (H2 HOL / H3 loss)

Windows heavier lossy

Userspace 5 ms one-way delay + 1% TCP connection stall (H1/H2) or UDP datagram drop (H3); 64 KiB GET. Median of 3 repeats on windows-latest @ 84b225f7 — 34355162890 (compare-lossy).

Client Origin TWP sustain TWP peak nginx sustain nginx peak HAProxy sustain HAProxy peak Envoy sustain Envoy peak YARP sustain YARP peak
HTTP/1 · TLS HTTP/1 · plain 662
(111 MiB / 5.0% CPU)
662
(111 MiB / 5.0% CPU)
628
(140 MiB / 19.1% CPU)
628
(140 MiB / 19.1% CPU)
Not possible Not possible Not possible Not possible 🥇 672
(115 MiB / 5.1% CPU)
672
(115 MiB / 5.1% CPU)
HTTP/2 · TLS HTTP/1 · plain 0
(126 MiB / 1.9% CPU)
86
(126 MiB / 1.9% CPU)
0
(140 MiB / 0.8% CPU)
17
(140 MiB / 0.8% CPU)
Not possible Not possible Not possible Not possible 0
(99 MiB / 0.5% CPU)
16
(99 MiB / 0.5% CPU)
HTTP/3 · QUIC HTTP/1 · plain 0
(66 MiB / 0.0% CPU)
0
(66 MiB / 0.0% CPU)
Not possible (no QUIC) Not possible (no QUIC) Not possible Not possible Not possible Not possible 0
(78 MiB / 0.0% CPU)
0
(78 MiB / 0.0% CPU)

TWP H2 HOL leads (~3.31× YARP). H3 is the protocol-shape win vs H2 HOL on the same lossy session; Win H3 GHA remains 0 (laptop remeasure kept above).

Linux — lossy / high-RTT (H2 HOL / H3 loss)

Linux heavier lossy

Median of 3 repeats @ 84b225f7. Source: 34355162890 (compare-lossy; lossy H3 uses quic-http3).

Client Origin TWP sustain TWP peak nginx sustain nginx peak HAProxy sustain HAProxy peak Envoy sustain Envoy peak YARP sustain YARP peak
HTTP/1 · TLS HTTP/1 · plain 1,213
(142 MiB / 8.5% CPU)
1,213
(142 MiB / 8.5% CPU)
🥇 1,221
(99 MiB / 6.0% CPU)
1,221
(99 MiB / 6.0% CPU)
Not measured Not measured Not measured Not measured 1,212
(152 MiB / 11.9% CPU)
1,212
(152 MiB / 11.9% CPU)
HTTP/2 · TLS HTTP/1 · plain 🥇 321
(188 MiB / 4.6% CPU)
321
(188 MiB / 4.6% CPU)
40
(99 MiB / 0.2% CPU)
40
(99 MiB / 0.2% CPU)
Not measured Not measured Not measured Not measured 40
(127 MiB / 0.9% CPU)
40
(127 MiB / 0.9% CPU)
HTTP/3 · QUIC HTTP/1 · plain 333
(146 MiB / 8.7% CPU)
333
(146 MiB / 8.7% CPU)
103
(110 MiB / 1.3% CPU)
103
(110 MiB / 1.3% CPU)
Not measured Not measured Not measured Not measured 🥇 353
(192 MiB / 14.7% CPU)
353
(192 MiB / 14.7% CPU)

TWP H2 HOL ≫ YARP (~7.7×). H3 TWP÷YARP ≈ 1×.

Architecture-sensitive

Windows heavier architecture-sensitive workloads

Linux heavier architecture-sensitive workloads

compare-arch isolates slow app readers, origin-early response, H2 duplex, and WebSocket echo. See TWP vs YARP IO model. Laptop 1-rep numbers are on Performance Local Lab.

Median of 3 repeats on matched 4 vCPU / 16 GiB runners @ 84b225f7 (34355169025) (compare-arch). Slow consumer = 256 KiB GET, 16 KiB read + 8 ms sleep. Early response = 64 KiB POST, origin writes after 8 KiB. Duplex H2 = overlapping 64 KiB POST on H2 TLS↔H2 TLS. WebSocket = echo round-trips/sec.

compare-lossy (slow network) is already published above; it is not a slow app reader.

Windows

Windows heavier arch

Scenario Client Origin TWP sustain TWP peak nginx sustain nginx peak HAProxy sustain HAProxy peak Envoy sustain Envoy peak YARP sustain YARP peak
Slow consumer (256 KiB GET, throttled client read) HTTP/1 · TLS HTTP/1 · plain 🥇 248
(100 MiB / 4.1% CPU)
248
(100 MiB / 4.1% CPU)
206
(142 MiB / 24.8% CPU)
206
(142 MiB / 24.8% CPU)
Not possible Not possible Not possible Not possible 240
(113 MiB / 4.2% CPU)
240
(113 MiB / 4.2% CPU)
Slow consumer (256 KiB GET, throttled client read) HTTP/2 · TLS HTTP/1 · plain 256
(122 MiB / 3.6% CPU)
256
(122 MiB / 3.6% CPU)
188
(140 MiB / 24.6% CPU)
188
(140 MiB / 24.6% CPU)
Not possible Not possible Not possible Not possible 🥇 256
(113 MiB / 6.5% CPU)
256
(113 MiB / 6.5% CPU)
Slow consumer (256 KiB GET, throttled client read) HTTP/3 · QUIC HTTP/1 · plain 🥇 271
(97 MiB / 17.2% CPU)
271
(97 MiB / 17.2% CPU)
Not possible (no QUIC) Not possible (no QUIC) Not possible Not possible Not possible Not possible 260
(166 MiB / 21.5% CPU)
260
(166 MiB / 21.5% CPU)
Early response (origin writes after first request chunk) HTTP/1 · TLS HTTP/1 · plain 🥇 6,137
(102 MiB / 43.5% CPU)
6,137
(102 MiB / 43.5% CPU)
383
(141 MiB / 24.6% CPU)
383
(141 MiB / 24.6% CPU)
Not possible Not possible Not possible Not possible 4,517
(138 MiB / 54.5% CPU)
4,517
(138 MiB / 54.5% CPU)
Early response (origin writes after first request chunk) HTTP/2 · TLS HTTP/1 · plain 🥇 5,068
(182 MiB / 48.1% CPU)
5,068
(182 MiB / 48.1% CPU)
0
(142 MiB / 25.0% CPU)
345
(142 MiB / 25.0% CPU)
Not possible Not possible Not possible Not possible 3,675
(134 MiB / 52.2% CPU)
3,675
(134 MiB / 52.2% CPU)
Early response (origin writes after first request chunk) HTTP/3 · QUIC HTTP/1 · plain 🥇 2,147
(149 MiB / 41.5% CPU)
2,147
(149 MiB / 41.5% CPU)
Not possible (no QUIC) Not possible (no QUIC) Not possible Not possible Not possible Not possible 1,923
(207 MiB / 51.9% CPU)
1,923
(207 MiB / 51.9% CPU)
Duplex (both directions live) HTTP/2 · TLS HTTP/2 · TLS 0
(90 MiB / 0.1% CPU)
0
(90 MiB / 0.1% CPU)
Not possible Not possible Not possible Not possible Not possible Not possible 0
(121 MiB / 0.3% CPU)
0
(121 MiB / 0.3% CPU)
Duplex (WebSocket / extended CONNECT) HTTP/1 · TLS HTTP/1 · plain 🥇 33,646
(97 MiB / 41.3% CPU)
33,646
(97 MiB / 41.3% CPU)
19,492
(141 MiB / 24.8% CPU)
19,492
(141 MiB / 24.8% CPU)
Not possible Not possible Not possible Not possible 30,989
(88 MiB / 44.0% CPU)
30,989
(88 MiB / 44.0% CPU)

Linux

Linux heavier arch

Scenario Client Origin TWP sustain TWP peak nginx sustain nginx peak HAProxy sustain HAProxy peak Envoy sustain Envoy peak YARP sustain YARP peak
Slow consumer (256 KiB GET, throttled client read) HTTP/1 · TLS HTTP/1 · plain 🥇 468
(117 MiB / 9.2% CPU)
468
(117 MiB / 9.2% CPU)
419
(97 MiB / 9.1% CPU)
419
(97 MiB / 9.1% CPU)
Not measured Not measured Not measured Not measured 427
(149 MiB / 13.2% CPU)
427
(149 MiB / 13.2% CPU)
Slow consumer (256 KiB GET, throttled client read) HTTP/2 · TLS HTTP/1 · plain 🥇 474
(143 MiB / 18.7% CPU)
474
(143 MiB / 18.7% CPU)
417
(98 MiB / 15.2% CPU)
417
(98 MiB / 15.2% CPU)
Not measured Not measured Not measured Not measured 470
(146 MiB / 22.4% CPU)
470
(146 MiB / 22.4% CPU)
Slow consumer (256 KiB GET, throttled client read) HTTP/3 · QUIC HTTP/1 · plain 🥇 474
(128 MiB / 34.3% CPU)
474
(128 MiB / 34.3% CPU)
0
(118 MiB / 22.8% CPU)
122
(118 MiB / 22.8% CPU)
Not measured Not measured Not measured Not measured 472
(189 MiB / 38.5% CPU)
472
(189 MiB / 38.5% CPU)
Early response (origin writes after first request chunk) HTTP/1 · TLS HTTP/1 · plain 🥇 5,002
(140 MiB / 45.7% CPU)
5,002
(140 MiB / 45.7% CPU)
4,002
(98 MiB / 49.4% CPU)
4,002
(98 MiB / 49.4% CPU)
Not measured Not measured Not measured Not measured 3,412
(175 MiB / 55.6% CPU)
3,412
(175 MiB / 55.6% CPU)
Early response (origin writes after first request chunk) HTTP/2 · TLS HTTP/1 · plain 🥇 3,683
(231 MiB / 47.6% CPU)
3,683
(231 MiB / 47.6% CPU)
0
(114 MiB / 24.5% CPU)
1,617
(114 MiB / 24.5% CPU)
Not measured Not measured Not measured Not measured 2,464
(165 MiB / 48.2% CPU)
2,464
(165 MiB / 48.2% CPU)
Early response (origin writes after first request chunk) HTTP/3 · QUIC HTTP/1 · plain 🥇 3,103
(209 MiB / 45.6% CPU)
3,103
(209 MiB / 45.6% CPU)
0
(113 MiB / 24.8% CPU)
555
(113 MiB / 24.8% CPU)
Not measured Not measured Not measured Not measured 2,263
(258 MiB / 47.4% CPU)
2,263
(258 MiB / 47.4% CPU)
Duplex (both directions live) HTTP/2 · TLS HTTP/2 · TLS 0
(112 MiB / 0.1% CPU)
0
(112 MiB / 0.1% CPU)
Not possible Not possible Not possible Not possible Not possible Not possible 0
(149 MiB / 0.2% CPU)
0
(149 MiB / 0.2% CPU)
Duplex (WebSocket / extended CONNECT) HTTP/1 · TLS HTTP/1 · plain 34,674
(124 MiB / 44.6% CPU)
34,674
(124 MiB / 44.6% CPU)
🥇 38,800
(99 MiB / 36.5% CPU)
38,800
(99 MiB / 36.5% CPU)
Not measured Not measured Not measured Not measured 32,017
(124 MiB / 44.8% CPU)
32,017
(124 MiB / 44.8% CPU)

Slow consumer is sleep-bound; H1/H2/H3 sit in the same band. Early-response H1/H2/H3: TWP leads (H1 early ≈ 2.00× / 1.47× YARP Win/Linux). Duplex H2: YARP leads by design — Win ≈ 0.59× (1,270 / 2,135), Linux ≈ 0.15× (282 / 1,882); irreducible concurrent-copier cell (see IO model). WebSocket: TWP÷YARP Windows ≈ 1.06×; Linux nginx leads.

TLS termination cost (H1 TLS → cleartext origin)

Isolates keep-alive tiny GET vs new connection per request (handshake-dominated) vs keep-alive 256 KiB. Product comparison uses RPS and end-to-end latency; TWP can also capture ClientTlsTiming when TWP_RPS_CAPTURE_TLS=1 (child process) — nginx/YARP have no equivalent hook.

Windows TLS termination cost

Linux TLS termination cost

Windows

Median of 3 repeats on windows-latest @ 84b225f7. Source: Actions 34355173969 (compare-tls-cost). Absolute RPS on GHA swings hard; prefer TWP÷YARP.

Windows heavier TLS cost

Workload TWP sustain TWP peak nginx sustain nginx peak HAProxy sustain HAProxy peak Envoy sustain Envoy peak YARP sustain YARP peak
Keep-alive · tiny GET 🥇 36,005
(86 MiB / 45.7% CPU)
36,005
(86 MiB / 45.7% CPU)
17,169
(140 MiB / 24.9% CPU)
17,169
(140 MiB / 24.9% CPU)
Not possible Not possible Not possible Not possible 31,526
(102 MiB / 50.7% CPU)
31,526
(102 MiB / 50.7% CPU)
New-connection · tiny GET 🥇 957
(89 MiB / 9.3% CPU)
957
(89 MiB / 9.3% CPU)
0
(139 MiB / 24.3% CPU)
310
(139 MiB / 24.3% CPU)
Not possible Not possible Not possible Not possible 941
(116 MiB / 8.9% CPU)
941
(116 MiB / 8.9% CPU)
Keep-alive · 256 KiB GET 🥇 3,754
(135 MiB / 39.7% CPU)
3,754
(135 MiB / 39.7% CPU)
233
(141 MiB / 24.7% CPU)
233
(141 MiB / 24.7% CPU)
Not possible Not possible Not possible Not possible 3,304
(128 MiB / 45.5% CPU)
3,304
(128 MiB / 45.5% CPU)

Linux

Median of 3 repeats @ 84b225f7. Source: Actions 34355173969 (compare-tls-cost).

Linux heavier TLS cost

Workload TWP sustain TWP peak nginx sustain nginx peak HAProxy sustain HAProxy peak Envoy sustain Envoy peak YARP sustain YARP peak
Keep-alive · tiny GET 48,132
(111 MiB / 47.5% CPU)
48,132
(111 MiB / 47.5% CPU)
🥇 62,451
(102 MiB / 39.3% CPU)
62,451
(102 MiB / 39.3% CPU)
Not measured Not measured Not measured Not measured 42,804
(134 MiB / 49.3% CPU)
42,804
(134 MiB / 49.3% CPU)
New-connection · tiny GET 1,542
(126 MiB / 38.7% CPU)
1,542
(126 MiB / 38.7% CPU)
🥇 1,633
(102 MiB / 34.0% CPU)
1,633
(102 MiB / 34.0% CPU)
Not measured Not measured Not measured Not measured 1,547
(149 MiB / 37.1% CPU)
1,547
(149 MiB / 37.1% CPU)
Keep-alive · 256 KiB GET 🥇 4,816
(126 MiB / 29.8% CPU)
4,816
(126 MiB / 29.8% CPU)
3,341
(100 MiB / 38.3% CPU)
3,341
(100 MiB / 38.3% CPU)
Not measured Not measured Not measured Not measured 3,464
(170 MiB / 41.3% CPU)
3,464
(170 MiB / 41.3% CPU)

All three workloads are >1.00× YARP on both OS. nginx leads Linux keep-alive tiny and Linux new-connection; TWP is second, YARP third.

Other measurements

What Result
HTTPS TTFB vs direct (median, 14 hosts) Cold ≈ parity (−1 ms); warm −25 ms (proxy faster)
HTTP/1 loopback GET (no body intercept) ~128 µs, ~9.9 KB allocated / request
Basic example footprint (Release, after load) ~74 MB working set · ~24–29 MB private bytes
dotnet run -c Release --project benchmarks/Titanium.Web.Proxy.Benchmarks -- --filter '*Throughput*'

Local laptop BDN @ 9d7c2966 (Release):

Benchmark Setup Mean Allocated / op
HTTP/1 GET through proxy Passthrough 128 µs 9.9 KB
HTTP/2 multiplexed GETs 10 concurrent streams 253 µs / batch ~4.4 KB / request

Raising limits on large hosts

There is no artificial upper clamp on server defaults. Per-endpoint overrides:

Knob Scope Default Override
ProxyServer.MaxCachedConnections process, per upstream host 128 any ≥ 1
ProxyEndPoint.MaxCachedConnections endpoint → pool depth for that EP’s sessions null (use server) e.g. 256 on reverse EP
ProxyEndPoint.MaxConcurrentClients endpoint admission null (off) any ≥ 1
ResourceLimits.MaxConcurrentStreamsPerConnection H2 streams 256 ProxyResourceLimits.Create(...)
TransparentQuicProxyEndPoint.MaxInboundBidirectionalStreams H3 100 (probe uses 256) property on EP
ForwardCleartext transparent TLS terminate false true + decrypt
proxy.MaxCachedConnections = 512;
proxy.ResourceLimits = ProxyResourceLimits.Create(
    /* … */,
    maxConcurrentStreamsPerConnection: 1000,
    maxCachedConnectionsPerHost: 512,
    /* … */);

var ep = new TransparentProxyEndPoint(IPAddress.Any, 443, decryptSsl: true)
{
    ForwardHost = "127.0.0.1",
    ForwardPort = 8080,
    ForwardCleartext = true,
    MaxCachedConnections = 256,
    GenericCertificateName = "example.com"
};
ep.BeforeSslAuthenticate += (_, a) =>
{
    a.UpstreamHttpProtocol = UpstreamHttpProtocol.Http11;
    a.AllowHttpProtocolTranslation = true; // HTTP/2 client → HTTP/1 origin
    return Task.CompletedTask;
};

Clone this wiki locally