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

Throughput and footprint of Titanium as a reverse / edge proxy and as a decrypting (MITM) proxy, measured on the same harness against YARP, nginx, HAProxy, and Envoy where each OS can run them.

RPS is requests per second (gRPC tables use RPC/s). Numbers are Release builds on matched GitHub-hosted runners: Windows and Linux at 4 vCPU / 16 GiB, macOS at macos-15 Apple Silicon 3-core / 7 GB. We compare products within an OS, not OS against OS, so read within one table — absolute RPS is not comparable across operating systems. Not possible means that product cannot run that path on that OS; Not measured means the path exists but no published number yet.

For pooling knobs and certificate first-visit tuning, see Performance and pooling. Laptop cool A/B tables (not publishable) live on Performance Local Lab.

Why this comparison is fair

  • Same load generator, same origin process, and the same warmup / measure windows (2s / 8s) with the same concurrency ramp (8, 16, 32, 64).
  • Every reverse arm is three OS processes: load generator + origin + proxy. Origin-direct omits the proxy; peers are never in-process with the client.
  • Same runner class per table (windows-latest / ubuntu-latest / macos-15). Laptop numbers are never mixed into these tables.
  • Peers use equivalent TLS/ALPN and streaming-friendly settings on the same loopback shape. HAProxy and Envoy are Linux/macOS only — Not possible on Windows, so their columns are omitted there.
  • MITM (HTTPS decryption with forged certificates) is Titanium-only; peers cannot MITM. Those tables show Titanium MITM overhead versus its own reverse path on the same wires.
  • Tiny keep-alive GET (~56-byte JSON) is the industry RPS shape (same class as wrk / TechEmpower). It is also real for small JSON APIs and health checks.
  • Same-protocol H2↔H2 / H3↔H3 on that shape is Titanium’s best case: with interception off, Titanium copies frames instead of decoding and re-encoding headers (peers do a full HTTP decode). Medals there are not the typical reverse-proxy job.
  • Typical reverse is H1 TLS→H1 or H2→H1 (~1.1× YARP on Win/Linux tiny GET). With larger bodies, see Heavier reverse (H1-client rows re-measured @ 1632b059; other rows stay @ 41f4adee).

How to read the tables

  • Bold RPS is sustain (last concurrency that still met error/latency SLOs). When peak differs, it appears in the same cell as <sub>(peak N · …)</sub> with RSS/CPU. When sustain equals peak, peak is omitted.
  • 🥇 = best among OS-possible peers on that row (highest sustain RPS; on a tie, lower memory then lower CPU%). On tiny Reverse that is Titanium / nginx / YARP (plus HAProxy / Envoy on Linux/macOS). The same rule applies to heavier reverse, architecture-sensitive, TLS-cost, gRPC, and WebSocket peer tables. Gold on tiny GET is that frame-copy best case, not “Titanium is 1.7× on all reverse.” For larger-body H2→H2, see the heavier tables.
  • MITM tables are Titanium-only (no peer medal). Lite÷Reverse / Full÷Reverse = Titanium MITM sustain ÷ Titanium reverse sustain on the same Client×Origin pair — the overhead of decrypting and intercepting versus bare reverse, not versus nginx or YARP.
  • Not possible = cannot do that path. Not measured = path exists but no published number yet. When every row would be Not possible for a peer, that column is omitted and a note above the table explains why.

Contents

Measurement environment

All three OS use public-repo GitHub-hosted runners: Windows / Linux at 4 vCPU / 16 GiB / 14 GB SSD, macOS at macos-15 Apple Silicon (M1) 3-core / 7 GB (pinned, not macos-latest). macOS is Apple Silicon because that is what Mac users run today; the runner sizes differ, which is fine because we compare products within an OS. Same harness knobs: warmup 2s / measure 8s; concurrency 8, 16, 32, 64; median of 3 repeats. Prefer Titanium÷YARP / Titanium÷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.

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, Apple Silicon)

OS macOS 15 (GitHub-hosted macos-15, Apple Silicon arm64)
CPU 3 logical processors (Apple M1)
RAM 7 GB
Runtime .NET 10.0.x
nginx Homebrew nginx with --with-http_v3_module (workflow fails if missing)
HAProxy Homebrew haproxy with USE_QUIC (workflow fails if missing; 3.2.23 osx source fallback)
Envoy Homebrew bottle when present; else pinned darwin-arm64 1.36.7 (official GitHub assets are Linux-only). 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

Use the pinned macos-15 label (not macos-latest) so the image does not change between runs.

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. Block B: median of 3 repeats @ 41f4adee — 36853300134. Block A is a later re-measure @ 1632b059 — 37087762494. Block C is a later re-measure (see that heading). 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).

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 RPS % of origin-HttpClient
origin-direct dotnet-httpclient 82,438
(55 MiB / 45.7% CPU)
100.0%
origin-direct-bombardier bombardier 63,122
(56 MiB / 26.5% CPU)
76.6%
bare-reverse-http1 dotnet-httpclient 40,748
(62 MiB / 46.6% CPU)
49.4%
nginx-reverse-http1 dotnet-httpclient 24,652
(125 MiB / 24.9% CPU)
29.9%
yarp-reverse-http1 dotnet-httpclient 35,382
(85 MiB / 50.8% CPU)
42.9%
twp-reverse-http1 dotnet-httpclient 🥇 42,430
(72 MiB / 47.7% CPU)
51.5%

Linux (ubuntu-latest)

Arm Generator RPS % of origin-HttpClient
origin-direct dotnet-httpclient 71,431
(79 MiB / 42.8% CPU)
100.0%
origin-direct-bombardier bombardier 41,837
(80 MiB / 35.2% CPU)
58.6%
bare-reverse-http1 dotnet-httpclient 32,579
(68 MiB / 44.7% CPU)
45.6%
nginx-reverse-http1 dotnet-httpclient 🥇 39,007
(76 MiB / 40.6% CPU)
54.6%
yarp-reverse-http1 dotnet-httpclient 27,655
(116 MiB / 49.8% CPU)
38.7%
twp-reverse-http1 dotnet-httpclient 31,915
(81 MiB / 48.9% CPU)
44.7%

macOS (macos-15, Apple Silicon)

Arm Generator RPS % of origin-HttpClient
origin-direct dotnet-httpclient 17,375
(88 MiB / 6.9% CPU)
100.0%
origin-direct-bombardier bombardier 55,352
(88 MiB / 22.7% CPU)
318.6%
bare-reverse-http1 dotnet-httpclient 27,744
(91 MiB / 24.1% CPU)
159.7%
nginx-reverse-http1 dotnet-httpclient 🥇 31,495
(67 MiB / 16.9% CPU)
181.3%
yarp-reverse-http1 dotnet-httpclient 29,758
(147 MiB / 29.9% CPU)
171.3%
twp-reverse-http1 dotnet-httpclient 30,759
(113 MiB / 28.2% CPU)
177.0%

On this macOS run origin-direct was 6.9% CPU, so the % of origin-HttpClient column is not a ceiling when that CPU is near idle. Rank those Mac peers by RPS.

Reverse peers on Block A @ 1632b059: Windows TWP is 51.5% of origin-direct and Linux TWP is 44.7%. 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 RPS ÷YARP ÷nginx
nginx-reverse-http2 dotnet-httpclient 7,915
(142 MiB / 24.7% CPU)
0.27× 1.00×
yarp-reverse-http2 dotnet-httpclient 28,934
(95 MiB / 53.9% CPU)
1.00× 3.66×
twp-reverse-http2-cleartext dotnet-httpclient 🥇 35,415
(95 MiB / 50.6% CPU)
1.22× 4.47×

Linux (ubuntu-latest)

Arm Generator RPS ÷YARP ÷nginx
nginx-reverse-http2 dotnet-httpclient 14,622
(100 MiB / 19.3% CPU)
0.51× 1.00×
yarp-reverse-http2 dotnet-httpclient 28,734
(122 MiB / 50.0% CPU)
1.00× 1.97×
twp-reverse-http2-cleartext dotnet-httpclient 🥇 33,677
(116 MiB / 51.1% CPU)
1.17× 2.30×

macOS (macos-15, Apple Silicon)

Arm Generator RPS ÷YARP ÷nginx
nginx-reverse-http2 dotnet-httpclient 19,705
(82 MiB / 22.9% CPU)
0.62× 1.00×
yarp-reverse-http2 dotnet-httpclient 🥇 31,903
(140 MiB / 43.2% CPU)
1.00× 1.62×
twp-reverse-http2-cleartext dotnet-httpclient 31,623
(118 MiB / 41.3% CPU)
0.99× 1.60×

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/macOS require it). Envoy 1.20+ includes HTTP/3.

Re-measured @ f2061c27 after the per-stream HTTP/3 write scratch — Windows 37005020668, Linux 37005016067. Medals are still nginx / YARP / Titanium only. On the Linux run HAProxy was 22,785 (86 MiB / 27.1% CPU) and Envoy 3,669 (134 MiB / 24.3% CPU); HAProxy is still ahead of Titanium. The TLS-origin twin is not in this table (the 5×5 below stays @ 41f4adee); that re-measure is in Performance Profiling. macOS Apple Silicon: 37011523949.

Windows (windows-latest)

Arm Generator RPS ÷YARP ÷nginx
nginx-reverse-http3-cleartext dotnet-httpclient Not possible (no QUIC) — —
yarp-reverse-http3-cleartext dotnet-httpclient 14,729
(141 MiB / 50.6% CPU)
1.00× —
twp-reverse-http3-cleartext dotnet-httpclient 🥇 15,068
(104 MiB / 45.2% CPU)
1.02× —

Linux (ubuntu-latest)

Arm Generator RPS ÷YARP ÷nginx
nginx-reverse-http3-cleartext dotnet-httpclient 0
(peak 15,485 · 106 MiB / 22.3% CPU)
0.84× 1.00×
yarp-reverse-http3-cleartext dotnet-httpclient 18,511
(183 MiB / 49.5% CPU)
1.00× 1.20×
twp-reverse-http3-cleartext dotnet-httpclient 🥇 20,833
(142 MiB / 49.8% CPU)
1.13× 1.35×

macOS (macos-15, Apple Silicon)

Arm Generator RPS ÷YARP ÷nginx
nginx-reverse-http3-cleartext dotnet-httpclient 0
(peak 20,873 · 88 MiB / 19.0% CPU)
1.58× 1.00×
yarp-reverse-http3-cleartext dotnet-httpclient 13,182
(215 MiB / 42.5% CPU)
1.00× 0.63×
twp-reverse-http3-cleartext dotnet-httpclient 🥇 15,605
(126 MiB / 43.1% CPU)
1.18× 0.75×

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 @ 41f4adee — compare-product 36853254836. 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. Product 5×5 is ~56-byte JSON keep-alive GET; H2/H3 same-protocol cells are mostly header work with a tiny body (Titanium best case) — see Why this comparison is fair.

H1-client rows (plain and TLS, reverse and MITM) were re-measured @ 1632b059 (run); other rows stay at the SHA above.

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.

Client Origin TWP nginx YARP
HTTP/1 · plain HTTP/1 · plain 🥇 26,435
(73 MiB / 47.2% CPU)
13,730
(125 MiB / 24.7% CPU)
21,749
(87 MiB / 50.4% CPU)
HTTP/1 · plain HTTP/1 · TLS 🥇 21,788
(84 MiB / 51% CPU)
8,530
(135 MiB / 24.7% CPU)
19,518
(100 MiB / 52.1% CPU)
HTTP/1 · plain HTTP/2 · plain 🥇 48,182
(110 MiB / 47.8% CPU)
Not possible (no H2 upstream) 42,621
(93 MiB / 49.2% CPU)
HTTP/1 · plain HTTP/2 · TLS 🥇 32,909
(118 MiB / 45.1% CPU)
Not possible (no H2 upstream) 30,006
(96 MiB / 46.6% CPU)
HTTP/1 · plain HTTP/3 · QUIC 🥇 18,481
(106 MiB / 50.6% CPU)
Not possible (no H3 upstream) 17,986
(120 MiB / 51.9% CPU)
HTTP/1 · TLS HTTP/1 · plain 🥇 21,428
(85 MiB / 49.2% CPU)
8,713
(142 MiB / 24.5% CPU)
18,339
(102 MiB / 50.4% CPU)
HTTP/1 · TLS HTTP/1 · TLS 🥇 19,382
(86 MiB / 47.7% CPU)
6,932
(144 MiB / 24.7% CPU)
16,569
(102 MiB / 46% CPU)
HTTP/1 · TLS HTTP/2 · plain 🥇 28,276
(111 MiB / 45.6% CPU)
Not possible (no H2 upstream) 26,212
(112 MiB / 46.9% CPU)
HTTP/1 · TLS HTTP/2 · TLS 🥇 33,428
(115 MiB / 43.8% CPU)
Not possible (no H2 upstream) 31,451
(108 MiB / 48% CPU)
HTTP/1 · TLS HTTP/3 · QUIC 🥇 16,002
(109 MiB / 50.8% CPU)
Not possible (no H3 upstream) 15,531
(126 MiB / 50.4% CPU)
HTTP/2 · plain HTTP/1 · plain 🥇 36,594
(86 MiB / 51.9% CPU)
9,135
(127 MiB / 24.7% CPU)
32,083
(86 MiB / 53.7% CPU)
HTTP/2 · plain HTTP/1 · TLS 🥇 38,670
(99 MiB / 49.9% CPU)
9,822
(137 MiB / 24.8% CPU)
33,958
(92 MiB / 49.4% CPU)
HTTP/2 · plain HTTP/2 · plain 🥇 109,500
(59 MiB / 29.9% CPU)
Not possible (no H2 upstream) 65,061
(94 MiB / 48.2% CPU)
HTTP/2 · plain HTTP/2 · TLS 🥇 86,785
(68 MiB / 28.5% CPU)
Not possible (no H2 upstream) 51,561
(101 MiB / 44.1% CPU)
HTTP/2 · plain HTTP/3 · QUIC 🥇 34,582
(127 MiB / 53.5% CPU)
Not possible (no H3 upstream) 32,128
(127 MiB / 50.6% CPU)
HTTP/2 · TLS HTTP/1 · plain 🥇 35,805
(96 MiB / 51.7% CPU)
8,246
(141 MiB / 24.6% CPU)
29,300
(95 MiB / 53.7% CPU)
HTTP/2 · TLS HTTP/1 · TLS 🥇 30,622
(101 MiB / 52.4% CPU)
6,318
(145 MiB / 24.7% CPU)
25,261
(98 MiB / 52.6% CPU)
HTTP/2 · TLS HTTP/2 · plain 🥇 95,497
(76 MiB / 28.9% CPU)
Not possible (no H2 upstream) 58,094
(103 MiB / 50.6% CPU)
HTTP/2 · TLS HTTP/2 · TLS 🥇 83,912
(74 MiB / 29.6% CPU)
Not possible (no H2 upstream) 49,700
(98 MiB / 49% CPU)
HTTP/2 · TLS HTTP/3 · QUIC 🥇 30,854
(135 MiB / 53.3% CPU)
Not possible (no H3 upstream) 26,509
(123 MiB / 53.3% CPU)
HTTP/3 · QUIC HTTP/1 · plain 🥇 20,480
(108 MiB / 43% CPU)
Not possible (no QUIC) 18,402
(146 MiB / 50.5% CPU)
HTTP/3 · QUIC HTTP/1 · TLS 🥇 13,870
(110 MiB / 45.2% CPU)
Not possible (no QUIC) 13,299
(146 MiB / 49.3% CPU)
HTTP/3 · QUIC HTTP/2 · plain 🥇 34,720
(126 MiB / 47.6% CPU)
Not possible (no H2 upstream) 23,072
(151 MiB / 49.6% CPU)
HTTP/3 · QUIC HTTP/2 · TLS 🥇 34,810
(137 MiB / 47% CPU)
Not possible (no H2 upstream) 21,949
(150 MiB / 47% CPU)
HTTP/3 · QUIC HTTP/3 · QUIC 🥇 16,836
(121 MiB / 40.3% CPU)
Not possible (no H3 upstream) 12,462
(156 MiB / 47.8% CPU)

MITM (TWP only)

Same Client×Origin wires with interception on (compare-product 36853254836). 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 / comparison-group shard). Completion gate: Lite ≥ 0.25× and Full ≥ 0.25× reverse sustain @ c=64 (median of 3 GHA runs); reverse TWP ÷ closest peer (YARP, nginx, HAProxy, or Envoy) ≥ 0.50×.

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. @ 41f4adee: 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 25,736
(76 MiB / 49.4% CPU)
25,154
(77 MiB / 52% CPU)
0.97× 0.95×
HTTP/1 · plain HTTP/1 · TLS 21,571
(92 MiB / 53.3% CPU)
21,047
(92 MiB / 54.4% CPU)
0.99× 0.97×
HTTP/1 · plain HTTP/2 · plain 40,940
(102 MiB / 47.9% CPU)
46,410
(114 MiB / 50.1% CPU)
0.85× 0.96×
HTTP/1 · plain HTTP/2 · TLS 32,079
(119 MiB / 47.2% CPU)
31,772
(121 MiB / 44.2% CPU)
0.97× 0.97×
HTTP/1 · plain HTTP/3 · QUIC 18,244
(107 MiB / 52.9% CPU)
17,950
(107 MiB / 51.5% CPU)
0.99× 0.97×
HTTP/1 · TLS HTTP/1 · plain 20,872
(91 MiB / 49% CPU)
20,582
(88 MiB / 48.5% CPU)
0.97× 0.96×
HTTP/1 · TLS HTTP/1 · TLS 19,325
(92 MiB / 49.2% CPU)
18,827
(90 MiB / 51.1% CPU)
1× 0.97×
HTTP/1 · TLS HTTP/2 · plain 28,104
(117 MiB / 45.4% CPU)
27,256
(113 MiB / 46.4% CPU)
0.99× 0.96×
HTTP/1 · TLS HTTP/2 · TLS 33,145
(121 MiB / 46.7% CPU)
32,502
(118 MiB / 46.2% CPU)
0.99× 0.97×
HTTP/1 · TLS HTTP/3 · QUIC 15,961
(111 MiB / 49.4% CPU)
15,795
(116 MiB / 49.6% CPU)
1× 0.99×
HTTP/2 · plain HTTP/1 · plain 35,159
(96 MiB / 55.4% CPU)
34,094
(96 MiB / 54.9% CPU)
0.96× 0.93×
HTTP/2 · plain HTTP/1 · TLS 36,635
(105 MiB / 51.8% CPU)
36,515
(101 MiB / 49.4% CPU)
0.95× 0.94×
HTTP/2 · plain HTTP/2 · plain 84,445
(71 MiB / 40.3% CPU)
80,594
(67 MiB / 41.7% CPU)
0.77× 0.74×
HTTP/2 · plain HTTP/2 · TLS 71,243
(70 MiB / 37.3% CPU)
67,741
(75 MiB / 40% CPU)
0.82× 0.78×
HTTP/2 · plain HTTP/3 · QUIC 34,577
(129 MiB / 53.4% CPU)
33,498
(136 MiB / 55.7% CPU)
1× 0.97×
HTTP/2 · TLS HTTP/1 · plain 33,615
(98 MiB / 53.5% CPU)
33,523
(98 MiB / 54.9% CPU)
0.94× 0.94×
HTTP/2 · TLS HTTP/1 · TLS 28,964
(102 MiB / 53.2% CPU)
28,825
(101 MiB / 55.5% CPU)
0.95× 0.94×
HTTP/2 · TLS HTTP/2 · plain 85,236
(82 MiB / 39% CPU)
82,696
(87 MiB / 40.9% CPU)
0.89× 0.87×
HTTP/2 · TLS HTTP/2 · TLS 68,466
(77 MiB / 38.5% CPU)
65,341
(80 MiB / 40.8% CPU)
0.82× 0.78×
HTTP/2 · TLS HTTP/3 · QUIC 30,486
(124 MiB / 52.6% CPU)
29,988
(137 MiB / 54.1% CPU)
0.99× 0.97×
HTTP/3 · QUIC HTTP/1 · plain 20,113
(119 MiB / 45.2% CPU)
19,554
(117 MiB / 44.1% CPU)
0.98× 0.95×
HTTP/3 · QUIC HTTP/1 · TLS 13,347
(120 MiB / 46.8% CPU)
12,596
(123 MiB / 45.8% CPU)
0.96× 0.91×
HTTP/3 · QUIC HTTP/2 · plain 33,868
(129 MiB / 46.6% CPU)
32,694
(131 MiB / 46.4% CPU)
0.98× 0.94×
HTTP/3 · QUIC HTTP/2 · TLS 33,817
(142 MiB / 47.2% CPU)
32,979
(138 MiB / 46.2% CPU)
0.97× 0.95×
HTTP/3 · QUIC HTTP/3 · QUIC 16,282
(116 MiB / 48.1% CPU)
15,803
(118 MiB / 45.6% CPU)
0.97× 0.94×

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 @ 41f4adee — compare-product 36853254836. 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. Product 5×5 is ~56-byte JSON keep-alive GET; H2/H3 same-protocol cells are mostly header work with a tiny body (Titanium best case) — see Why this comparison is fair.

H1-client rows (plain and TLS, reverse and MITM) were re-measured @ 1632b059 (run); other rows stay at the SHA above.

Client Origin TWP nginx HAProxy Envoy YARP
HTTP/1 · plain HTTP/1 · plain 46,130
(80 MiB / 49% CPU)
🥇 56,010
(76 MiB / 39.6% CPU)
52,546
(64 MiB / 41.7% CPU)
31,703
(116 MiB / 58.5% CPU)
41,088
(116 MiB / 48.5% CPU)
HTTP/1 · plain HTTP/1 · TLS 37,832
(106 MiB / 47.9% CPU)
🥇 44,785
(93 MiB / 41.5% CPU)
43,121
(69 MiB / 42.6% CPU)
28,726
(118 MiB / 56.1% CPU)
33,726
(128 MiB / 49.2% CPU)
HTTP/1 · plain HTTP/2 · plain 🥇 42,126
(124 MiB / 50.1% CPU)
Not possible (no H2 upstream) 27,642
(66 MiB / 42.6% CPU)
21,554
(116 MiB / 64.2% CPU)
35,843
(123 MiB / 49.5% CPU)
HTTP/1 · plain HTTP/2 · TLS 🥇 38,179
(142 MiB / 49.2% CPU)
Not possible (no H2 upstream) 34,950
(66 MiB / 43.2% CPU)
25,265
(117 MiB / 59.6% CPU)
35,038
(131 MiB / 47.5% CPU)
HTTP/1 · plain HTTP/3 · QUIC 🥇 50,848
(164 MiB / 54.4% CPU)
Not possible (no H3 upstream) Not possible (no H3 upstream) 29,583
(121 MiB / 48.8% CPU)
43,839
(170 MiB / 47% CPU)
HTTP/1 · TLS HTTP/1 · plain 37,586
(107 MiB / 48.6% CPU)
42,785
(102 MiB / 41.4% CPU)
🥇 43,123
(83 MiB / 43.5% CPU)
27,782
(127 MiB / 56.7% CPU)
32,246
(134 MiB / 49.1% CPU)
HTTP/1 · TLS HTTP/1 · TLS 32,300
(108 MiB / 47.4% CPU)
🥇 36,742
(105 MiB / 41% CPU)
36,090
(84 MiB / 43.3% CPU)
24,991
(127 MiB / 54.6% CPU)
27,943
(135 MiB / 49.1% CPU)
HTTP/1 · TLS HTTP/2 · plain 🥇 69,681
(151 MiB / 49.9% CPU)
Not possible (no H2 upstream) 47,137
(84 MiB / 42.8% CPU)
54,016
(126 MiB / 52.9% CPU)
62,753
(142 MiB / 48.7% CPU)
HTTP/1 · TLS HTTP/2 · TLS 🥇 25,328
(154 MiB / 47.6% CPU)
Not possible (no H2 upstream) 23,716
(83 MiB / 43.3% CPU)
17,369
(126 MiB / 58.4% CPU)
22,175
(140 MiB / 48.9% CPU)
HTTP/1 · TLS HTTP/3 · QUIC 🥇 21,737
(150 MiB / 53.2% CPU)
Not possible (no H3 upstream) Not possible (no H3 upstream) 11,174
(130 MiB / 55.1% CPU)
19,611
(160 MiB / 49.6% CPU)
HTTP/2 · plain HTTP/1 · plain 🥇 77,078
(120 MiB / 52.3% CPU)
29,472
(79 MiB / 18.5% CPU)
46,455
(67 MiB / 24.4% CPU)
33,118
(118 MiB / 22.9% CPU)
70,854
(112 MiB / 47.4% CPU)
HTTP/2 · plain HTTP/1 · TLS 🥇 29,364
(125 MiB / 50.8% CPU)
13,245
(101 MiB / 19.5% CPU)
18,930
(71 MiB / 24.6% CPU)
13,147
(118 MiB / 23.5% CPU)
26,309
(128 MiB / 50.9% CPU)
HTTP/2 · plain HTTP/2 · plain 🥇 95,132
(90 MiB / 38% CPU)
Not possible (no H2 upstream) 29,728
(66 MiB / 24.3% CPU)
24,027
(115 MiB / 21.6% CPU)
54,791
(132 MiB / 47% CPU)
HTTP/2 · plain HTTP/2 · TLS 🥇 130,965
(88 MiB / 41.3% CPU)
Not possible (no H2 upstream) 42,285
(68 MiB / 24.5% CPU)
41,750
(118 MiB / 20.9% CPU)
88,980
(132 MiB / 45% CPU)
HTTP/2 · plain HTTP/3 · QUIC 🥇 28,674
(144 MiB / 50.3% CPU)
Not possible (no H3 upstream) Not possible (no H3 upstream) 4,339
(122 MiB / 25% CPU)
26,529
(162 MiB / 46.4% CPU)
HTTP/2 · TLS HTTP/1 · plain 🥇 39,560
(128 MiB / 51.7% CPU)
17,620
(100 MiB / 19% CPU)
24,964
(81 MiB / 24.3% CPU)
17,152
(129 MiB / 22.7% CPU)
33,979
(122 MiB / 48.6% CPU)
HTTP/2 · TLS HTTP/1 · TLS 🥇 59,236
(133 MiB / 49.1% CPU)
27,000
(112 MiB / 19.1% CPU)
38,045
(86 MiB / 24.6% CPU)
30,525
(128 MiB / 22.6% CPU)
54,530
(126 MiB / 46.9% CPU)
HTTP/2 · TLS HTTP/2 · plain 🥇 73,087
(95 MiB / 35.9% CPU)
Not possible (no H2 upstream) 21,291
(82 MiB / 24.4% CPU)
17,672
(125 MiB / 22.1% CPU)
40,036
(127 MiB / 47.6% CPU)
HTTP/2 · TLS HTTP/2 · TLS 🥇 61,583
(100 MiB / 35.4% CPU)
Not possible (no H2 upstream) 23,639
(83 MiB / 24.2% CPU)
19,934
(127 MiB / 20.6% CPU)
38,518
(127 MiB / 45.6% CPU)
HTTP/2 · TLS HTTP/3 · QUIC 🥇 54,162
(190 MiB / 52.7% CPU)
Not possible (no H3 upstream) Not possible (no H3 upstream) 16,762
(129 MiB / 24.7% CPU)
47,112
(176 MiB / 44.5% CPU)
HTTP/3 · QUIC HTTP/1 · plain 21,551
(145 MiB / 50% CPU)
0
(peak 15,107 · 107 MiB / 22.6% CPU)
🥇 24,083
(86 MiB / 26.1% CPU)
3,989
(132 MiB / 24.5% CPU)
18,996
(186 MiB / 50.3% CPU)
HTTP/3 · QUIC HTTP/1 · TLS 19,461
(163 MiB / 48.5% CPU)
0
(peak 14,824 · 117 MiB / 22.7% CPU)
🥇 21,322
(90 MiB / 28.9% CPU)
4,755
(136 MiB / 24.3% CPU)
18,172
(199 MiB / 49.7% CPU)
HTTP/3 · QUIC HTTP/2 · plain 🥇 60,482
(201 MiB / 55.6% CPU)
Not possible (no H2 upstream) 54,740
(88 MiB / 25.2% CPU)
20
(130 MiB / 0.1% CPU)
51,309
(226 MiB / 47.2% CPU)
HTTP/3 · QUIC HTTP/2 · TLS 🥇 25,276
(155 MiB / 50.1% CPU)
Not possible (no H2 upstream) 21,371
(86 MiB / 26.5% CPU)
6
(129 MiB / 0.1% CPU)
21,601
(193 MiB / 48% CPU)
HTTP/3 · QUIC HTTP/3 · QUIC 🥇 22,007
(157 MiB / 47.7% CPU)
Not possible (no H3 upstream) Not possible (no H3 upstream) 2,856
(130 MiB / 24.9% CPU)
17,736
(207 MiB / 46.7% CPU)

MITM (TWP only)

Same Client×Origin wires with interception on (compare-product 36853254836). 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 / comparison-group shard). Completion gate: Lite ≥ 0.25× and Full ≥ 0.25× reverse sustain @ c=64 (median of 3 GHA runs); reverse TWP ÷ closest peer (YARP, nginx, HAProxy, or Envoy) ≥ 0.50×.

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. @ 41f4adee: 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 46,964
(94 MiB / 49.7% CPU)
45,379
(89 MiB / 49.5% CPU)
1.02× 0.98×
HTTP/1 · plain HTTP/1 · TLS 38,316
(111 MiB / 49.1% CPU)
37,240
(110 MiB / 48.9% CPU)
1.01× 0.98×
HTTP/1 · plain HTTP/2 · plain 40,743
(131 MiB / 53% CPU)
39,266
(127 MiB / 53.2% CPU)
0.97× 0.93×
HTTP/1 · plain HTTP/2 · TLS 37,310
(144 MiB / 52.2% CPU)
36,668
(152 MiB / 50.5% CPU)
0.98× 0.96×
HTTP/1 · plain HTTP/3 · QUIC 49,696
(166 MiB / 56% CPU)
49,175
(164 MiB / 55.2% CPU)
0.98× 0.97×
HTTP/1 · TLS HTTP/1 · plain 37,330
(116 MiB / 49% CPU)
36,512
(112 MiB / 48.8% CPU)
0.99× 0.97×
HTTP/1 · TLS HTTP/1 · TLS 32,065
(118 MiB / 48% CPU)
31,556
(115 MiB / 48.2% CPU)
0.99× 0.98×
HTTP/1 · TLS HTTP/2 · plain 67,032
(166 MiB / 50.1% CPU)
65,781
(158 MiB / 51% CPU)
0.96× 0.94×
HTTP/1 · TLS HTTP/2 · TLS 23,602
(163 MiB / 49.4% CPU)
23,843
(170 MiB / 48.6% CPU)
0.93× 0.94×
HTTP/1 · TLS HTTP/3 · QUIC 21,552
(151 MiB / 53% CPU)
20,790
(156 MiB / 53.9% CPU)
0.99× 0.96×
HTTP/2 · plain HTTP/1 · plain 76,412
(124 MiB / 52.9% CPU)
73,763
(120 MiB / 53% CPU)
0.99× 0.96×
HTTP/2 · plain HTTP/1 · TLS 28,157
(124 MiB / 52% CPU)
27,650
(129 MiB / 52.1% CPU)
0.96× 0.94×
HTTP/2 · plain HTTP/2 · plain 71,938
(94 MiB / 43.2% CPU)
66,304
(95 MiB / 42.9% CPU)
0.76× 0.7×
HTTP/2 · plain HTTP/2 · TLS 111,003
(103 MiB / 44.1% CPU)
108,771
(106 MiB / 41.6% CPU)
0.85× 0.83×
HTTP/2 · plain HTTP/3 · QUIC 28,202
(145 MiB / 50.3% CPU)
27,788
(144 MiB / 50.5% CPU)
0.98× 0.97×
HTTP/2 · TLS HTTP/1 · plain 37,310
(124 MiB / 52.7% CPU)
37,209
(127 MiB / 52.4% CPU)
0.94× 0.94×
HTTP/2 · TLS HTTP/1 · TLS 57,688
(133 MiB / 49.9% CPU)
55,972
(134 MiB / 49.4% CPU)
0.97× 0.94×
HTTP/2 · TLS HTTP/2 · plain 56,665
(102 MiB / 39.9% CPU)
53,179
(111 MiB / 40.9% CPU)
0.78× 0.73×
HTTP/2 · TLS HTTP/2 · TLS 51,615
(111 MiB / 40.1% CPU)
48,871
(113 MiB / 39.6% CPU)
0.84× 0.79×
HTTP/2 · TLS HTTP/3 · QUIC 52,800
(185 MiB / 51.8% CPU)
51,956
(189 MiB / 52.1% CPU)
0.97× 0.96×
HTTP/3 · QUIC HTTP/1 · plain 20,377
(143 MiB / 50.5% CPU)
20,027
(140 MiB / 50.4% CPU)
0.95× 0.93×
HTTP/3 · QUIC HTTP/1 · TLS 18,610
(168 MiB / 48.9% CPU)
18,240
(168 MiB / 48.8% CPU)
0.96× 0.94×
HTTP/3 · QUIC HTTP/2 · plain 58,870
(200 MiB / 57.1% CPU)
59,642
(195 MiB / 56.5% CPU)
0.97× 0.99×
HTTP/3 · QUIC HTTP/2 · TLS 25,006
(155 MiB / 50.1% CPU)
24,294
(158 MiB / 50.2% CPU)
0.99× 0.96×
HTTP/3 · QUIC HTTP/3 · QUIC 21,690
(163 MiB / 50.4% CPU)
20,322
(161 MiB / 50.5% CPU)
0.99× 0.92×

macOS — Titanium vs nginx vs HAProxy vs Envoy vs YARP

Reverse

Median of 3 repeats on macos-15 (Apple Silicon M1, 3-core / 7 GB). Bare reverse 5×5 @ 41f4adee — compare-product 36982444617. 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 HAProxy with USE_QUIC (3.2 source fallback), Envoy (Homebrew bottle or pinned darwin-arm64 1.36.7), Homebrew libmsquic (+ DYLD_*), and YARP. Use the pinned macos-15 label, not macos-latest. Product 5×5 is ~56-byte JSON keep-alive GET; H2/H3 same-protocol cells are mostly header work with a tiny body (Titanium best case) — see Why this comparison is fair.

H1-client rows (plain and TLS, reverse and MITM) were re-measured @ 8107215a (run); that commit is the same product tree as 1632b059 and only raises the macOS ramp step timeout. Other rows stay at the SHA above.

Client Origin TWP nginx HAProxy Envoy YARP
HTTP/1 · plain HTTP/1 · plain 33,844
(113 MiB / 34.9% CPU)
🥇 49,390
(68 MiB / 24.5% CPU)
46,343
(83 MiB / 32.6% CPU)
24,277
(111 MiB / 44.6% CPU)
32,612
(147 MiB / 39.5% CPU)
HTTP/1 · plain HTTP/1 · TLS 29,995
(134 MiB / 36% CPU)
20,023
(79 MiB / 21.7% CPU)
33,349
(88 MiB / 31.2% CPU)
19,771
(114 MiB / 36.9% CPU)
🥇 33,509
(159 MiB / 42% CPU)
HTTP/1 · plain HTTP/2 · plain 32,773
(127 MiB / 29.2% CPU)
Not possible (no H2 upstream) 20,883
(84 MiB / 27.6% CPU)
21,625
(111 MiB / 31.8% CPU)
🥇 41,904
(163 MiB / 38.8% CPU)
HTTP/1 · plain HTTP/2 · TLS 33,673
(168 MiB / 37.2% CPU)
Not possible (no H2 upstream) 38,027
(85 MiB / 35.8% CPU)
25,071
(112 MiB / 45.3% CPU)
🥇 38,644
(162 MiB / 38.6% CPU)
HTTP/1 · plain HTTP/3 · QUIC 10,473
(133 MiB / 42.8% CPU)
Not possible (no H3 upstream) Not possible (no H3 upstream) 22,465
(116 MiB / 46.9% CPU)
🥇 33,872
(175 MiB / 42.6% CPU)
HTTP/1 · TLS HTTP/1 · plain 23,126
(128 MiB / 32.2% CPU)
25,379
(85 MiB / 23.9% CPU)
🥇 34,606
(101 MiB / 30.2% CPU)
20,330
(121 MiB / 36.1% CPU)
23,508
(156 MiB / 37.2% CPU)
HTTP/1 · TLS HTTP/1 · TLS 26,432
(149 MiB / 36.1% CPU)
15,508
(89 MiB / 22.6% CPU)
🥇 34,236
(103 MiB / 35.3% CPU)
20,970
(122 MiB / 44.2% CPU)
21,857
(163 MiB / 40% CPU)
HTTP/1 · TLS HTTP/2 · plain 12,874
(156 MiB / 29.4% CPU)
Not possible (no H2 upstream) 17,595
(101 MiB / 28.5% CPU)
17,894
(121 MiB / 42% CPU)
🥇 25,472
(187 MiB / 26% CPU)
HTTP/1 · TLS HTTP/2 · TLS 7,448
(209 MiB / 17.2% CPU)
Not possible (no H2 upstream) 🥇 20,146
(101 MiB / 30.3% CPU)
14,340
(121 MiB / 39.7% CPU)
4,544
(190 MiB / 9.9% CPU)
HTTP/1 · TLS HTTP/3 · QUIC 16,589
(156 MiB / 44.3% CPU)
Not possible (no H3 upstream) Not possible (no H3 upstream) 15,066
(125 MiB / 42.2% CPU)
🥇 20,502
(173 MiB / 39.9% CPU)
HTTP/2 · plain HTTP/1 · plain 🥇 45,158
(126 MiB / 40% CPU)
29,051
(69 MiB / 23.5% CPU)
29,130
(87 MiB / 23.7% CPU)
15,366
(111 MiB / 29.5% CPU)
35,765
(147 MiB / 39.4% CPU)
HTTP/2 · plain HTTP/1 · TLS 30,201
(140 MiB / 42.2% CPU)
18,471
(81 MiB / 23% CPU)
28,685
(92 MiB / 27.6% CPU)
13,578
(113 MiB / 27.4% CPU)
🥇 33,191
(159 MiB / 43.4% CPU)
HTTP/2 · plain HTTP/2 · plain 🥇 98,980
(116 MiB / 30% CPU)
Not possible (no H2 upstream) 34,877
(87 MiB / 27.6% CPU)
23,568
(109 MiB / 25.8% CPU)
58,570
(151 MiB / 42.5% CPU)
HTTP/2 · plain HTTP/2 · TLS 🥇 107,876
(125 MiB / 32.2% CPU)
Not possible (no H2 upstream) 33,056
(89 MiB / 26.7% CPU)
22,703
(111 MiB / 26.9% CPU)
83,722
(157 MiB / 42.3% CPU)
HTTP/2 · plain HTTP/3 · QUIC 14,700
(143 MiB / 35.1% CPU)
Not possible (no H3 upstream) Not possible (no H3 upstream) 10,618
(114 MiB / 30% CPU)
🥇 22,834
(163 MiB / 43.6% CPU)
HTTP/2 · TLS HTTP/1 · plain 35,882
(131 MiB / 41.1% CPU)
23,621
(81 MiB / 22.6% CPU)
34,868
(101 MiB / 25.3% CPU)
20,382
(121 MiB / 30.2% CPU)
🥇 39,986
(153 MiB / 42.5% CPU)
HTTP/2 · TLS HTTP/1 · TLS 22,919
(137 MiB / 42.2% CPU)
🥇 23,815
(88 MiB / 22% CPU)
23,102
(104 MiB / 27.6% CPU)
12,429
(122 MiB / 27.6% CPU)
18,991
(162 MiB / 37.8% CPU)
HTTP/2 · TLS HTTP/2 · plain 🥇 68,025
(131 MiB / 28.6% CPU)
Not possible (no H2 upstream) 25,181
(101 MiB / 27.5% CPU)
19,086
(119 MiB / 24.2% CPU)
39,591
(161 MiB / 35.7% CPU)
HTTP/2 · TLS HTTP/2 · TLS 🥇 94,817
(136 MiB / 32.7% CPU)
Not possible (no H2 upstream) 47,241
(102 MiB / 29% CPU)
31,349
(119 MiB / 26% CPU)
68,743
(157 MiB / 44.1% CPU)
HTTP/2 · TLS HTTP/3 · QUIC 20,008
(160 MiB / 47.8% CPU)
Not possible (no H3 upstream) Not possible (no H3 upstream) 10,706
(123 MiB / 30.9% CPU)
🥇 29,346
(168 MiB / 42.5% CPU)
HTTP/3 · QUIC HTTP/1 · plain 12,512
(138 MiB / 42.8% CPU)
0
(peak 18,612 · 88 MiB / 18.9% CPU)
🥇 30,575
(101 MiB / 29.4% CPU)
5,838
(129 MiB / 37.9% CPU)
18,511
(213 MiB / 45% CPU)
HTTP/3 · QUIC HTTP/1 · TLS 13,728
(152 MiB / 39.5% CPU)
0
(peak 18,482 · 93 MiB / 20.6% CPU)
🥇 22,531
(104 MiB / 31.5% CPU)
5,728
(130 MiB / 31% CPU)
12,918
(274 MiB / 45.7% CPU)
HTTP/3 · QUIC HTTP/2 · plain 20,661
(147 MiB / 47.8% CPU)
Not possible (no H2 upstream) 🥇 43,121
(101 MiB / 31% CPU)
12,596
(125 MiB / 39% CPU)
28,622
(219 MiB / 45.1% CPU)
HTTP/3 · QUIC HTTP/2 · TLS 20,776
(147 MiB / 49.2% CPU)
Not possible (no H2 upstream) 🥇 23,721
(102 MiB / 28.4% CPU)
7,012
(125 MiB / 37.3% CPU)
23,520
(222 MiB / 45.4% CPU)
HTTP/3 · QUIC HTTP/3 · QUIC 17,192
(144 MiB / 45.3% CPU)
Not possible (no H3 upstream) Not possible (no H3 upstream) 5,502
(125 MiB / 33.7% CPU)
🥇 19,828
(232 MiB / 44% CPU)

MITM (TWP only)

Same Client×Origin wires with interception on (compare-product 36982444617). 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 / comparison-group shard). Completion gate: Lite ≥ 0.25× and Full ≥ 0.25× reverse sustain @ c=64 (median of 3 GHA runs); reverse TWP ÷ closest peer (YARP, nginx, HAProxy, or Envoy) ≥ 0.50×.

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. @ 41f4adee: 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 28,098
(120 MiB / 33.3% CPU)
42,279
(114 MiB / 38.8% CPU)
0.83× 1.25×
HTTP/1 · plain HTTP/1 · TLS 36,700
(141 MiB / 39.4% CPU)
38,888
(128 MiB / 40.4% CPU)
1.22× 1.3×
HTTP/1 · plain HTTP/2 · plain 34,723
(144 MiB / 36.3% CPU)
45,571
(148 MiB / 42% CPU)
1.06× 1.39×
HTTP/1 · plain HTTP/2 · TLS 30,884
(166 MiB / 38.8% CPU)
30,650
(159 MiB / 37.9% CPU)
0.92× 0.91×
HTTP/1 · plain HTTP/3 · QUIC 22,609
(143 MiB / 46.4% CPU)
18,784
(150 MiB / 45.1% CPU)
2.16× 1.79×
HTTP/1 · TLS HTTP/1 · plain 33,132
(133 MiB / 37.5% CPU)
31,578
(129 MiB / 37.9% CPU)
1.43× 1.37×
HTTP/1 · TLS HTTP/1 · TLS 30,080
(147 MiB / 39.2% CPU)
30,512
(143 MiB / 39.6% CPU)
1.14× 1.15×
HTTP/1 · TLS HTTP/2 · plain 21,932
(138 MiB / 41.4% CPU)
24,957
(146 MiB / 38.8% CPU)
1.7× 1.94×
HTTP/1 · TLS HTTP/2 · TLS 22,604
(204 MiB / 38.1% CPU)
17,668
(190 MiB / 35.4% CPU)
3.03× 2.37×
HTTP/1 · TLS HTTP/3 · QUIC 16,816
(157 MiB / 45.6% CPU)
12,059
(157 MiB / 41.3% CPU)
1.01× 0.73×
HTTP/2 · plain HTTP/1 · plain 36,969
(129 MiB / 41.5% CPU)
32,390
(126 MiB / 41.6% CPU)
0.82× 0.72×
HTTP/2 · plain HTTP/1 · TLS 20,368
(143 MiB / 42.5% CPU)
26,284
(140 MiB / 43.6% CPU)
0.67× 0.87×
HTTP/2 · plain HTTP/2 · plain 78,223
(118 MiB / 37.4% CPU)
73,211
(118 MiB / 35.7% CPU)
0.79× 0.74×
HTTP/2 · plain HTTP/2 · TLS 98,122
(121 MiB / 37.3% CPU)
81,877
(125 MiB / 37% CPU)
0.91× 0.76×
HTTP/2 · plain HTTP/3 · QUIC 17,556
(142 MiB / 48.1% CPU)
18,565
(143 MiB / 50.8% CPU)
1.19× 1.26×
HTTP/2 · TLS HTTP/1 · plain 34,535
(133 MiB / 40.3% CPU)
33,251
(135 MiB / 41.6% CPU)
0.96× 0.93×
HTTP/2 · TLS HTTP/1 · TLS 27,152
(138 MiB / 44% CPU)
37,855
(140 MiB / 45.4% CPU)
1.18× 1.65×
HTTP/2 · TLS HTTP/2 · plain 66,090
(128 MiB / 35.8% CPU)
60,172
(130 MiB / 34.3% CPU)
0.97× 0.88×
HTTP/2 · TLS HTTP/2 · TLS 70,582
(132 MiB / 37.8% CPU)
75,340
(132 MiB / 35.7% CPU)
0.74× 0.79×
HTTP/2 · TLS HTTP/3 · QUIC 17,434
(160 MiB / 49.2% CPU)
17,244
(157 MiB / 49% CPU)
0.87× 0.86×
HTTP/3 · QUIC HTTP/1 · plain 14,529
(139 MiB / 45.5% CPU)
12,468
(138 MiB / 42.3% CPU)
1.16× 1×
HTTP/3 · QUIC HTTP/1 · TLS 13,631
(143 MiB / 45.8% CPU)
11,639
(148 MiB / 45.7% CPU)
0.99× 0.85×
HTTP/3 · QUIC HTTP/2 · plain 31,499
(149 MiB / 48.5% CPU)
20,464
(146 MiB / 49% CPU)
1.52× 0.99×
HTTP/3 · QUIC HTTP/2 · TLS 18,549
(148 MiB / 48.6% CPU)
17,494
(146 MiB / 45.2% CPU)
0.89× 0.84×
HTTP/3 · QUIC HTTP/3 · QUIC 9,942
(143 MiB / 45.6% CPU)
12,760
(140 MiB / 45.5% CPU)
0.58× 0.74×

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). Pre-origin Plus middleware (CIDR/WAF/JWT/rate-limit) runs on H1 terminate-lite without SessionEventArgs; JWT caches successful bearer validations. Maintainer gate thresholds live under Maintainer notes.

Median of 3 repeats @ 41f4adee. Source: Actions 36853305087. Warmup 2s / measure 8s; concurrency 8–64; sustain = median peak RPS among SLO-pass steps @ c=64. RPS cells show sustain; <sub> holds peak (when higher) plus (MiB / CPU%).

Arm Win Linux Win÷ Lin÷ Gate
twp-cli-reverse-http1 vs library 66,323
(131 MiB / 45.5% CPU)
34,106
(166 MiB / 50.4% CPU)
1.03× 1.08× ≥ 0.80×
twp-cli-reverse-http1-tls vs library TLS 56,976
(145 MiB / 45.7% CPU)
24,583
(197 MiB / 50.3% CPU)
1.00× 1.04× ≥ 0.80×
twp-cli-reverse-http1-route vs CLI 66,646
(135 MiB / 47.6% CPU)
33,126
(169 MiB / 50.1% CPU)
1.00× 0.97× ≥ 0.90×
twp-cli-plus-base-http1 vs CLI 66,656
(137 MiB / 47.2% CPU)
34,197
(170 MiB / 50.5% CPU)
1.01× 1.00× ≥ 0.90×
twp-cli-plus-cache-http1 (cold) vs CLI 58,304
(140 MiB / 50.0% CPU)
28,354
(175 MiB / 54.0% CPU)
0.88× 0.83× ≥ 0.70×
twp-cli-intercept-http1 vs CLI 53,188
(138 MiB / 49.4% CPU)
24,664
(176 MiB / 52.9% CPU)
0.80× 0.72× ≥ 0.70×
twp-cli-plus-waf-http1 vs CLI 63,745
(134 MiB / 45.3% CPU)
32,738
(170 MiB / 51.2% CPU)
0.96× 0.96× ≥ 0.80×
twp-cli-plus-cidr-http1 vs CLI 64,199
(133 MiB / 47.6% CPU)
32,903
(170 MiB / 51.0% CPU)
0.97× 0.96× ≥ 0.80×
twp-cli-plus-jwt-http1 vs CLI 61,258
(149 MiB / 45.8% CPU)
30,232
(190 MiB / 50.7% CPU)
0.92× 0.89× ≥ 0.70×
twp-cli-plus-ratelimit-http1 vs CLI 63,961
(141 MiB / 47.0% CPU)
32,384
(170 MiB / 51.2% CPU)
0.96× 0.95× ≥ 0.80×
twp-cli-plus-resilience-http1 vs CLI 65,742
(144 MiB / 48.0% CPU)
33,765
(174 MiB / 50.6% CPU)
0.99× 0.99× ≥ 0.85×
twp-cli-plus-discovery-file-http1 vs CLI 65,441
(139 MiB / 47.0% CPU)
34,153
(172 MiB / 50.6% CPU)
0.99× 1.00× ≥ 0.80×
twp-cli-plus-metrics-scrape-http1 vs CLI 65,919
(140 MiB / 46.8% CPU)
33,848
(175 MiB / 50.3% CPU)
0.99× 0.99× ≥ 0.80×
twp-cli-plus-cache-hit-http1 vs cache cold 58,870
(147 MiB / 48.7% CPU)
28,214
(174 MiB / 53.4% CPU)
1.01× 1.00× ≥ 0.90×
twp-cli-static-http1 vs CLI 114,525
(130 MiB / 49.1% CPU)
54,684
(162 MiB / 50.2% CPU)
1.73× 1.60× ≥ 0.85×
twp-cli-logging-http1 vs CLI 67,217
(139 MiB / 45.5% CPU)
33,844
(163 MiB / 50.8% CPU)
1.01× 0.99× ≥ 0.90×
twp-cli-lb-leasttime-http1 vs route 63,630
(144 MiB / 50.9% CPU)
31,242
(191 MiB / 52.4% CPU)
0.95× 0.94× ≥ 0.85×
twp-cli-dialect-twp-http1 vs CLI 66,493
(134 MiB / 46.4% CPU)
33,965
(158 MiB / 50.3% CPU)
1.00× 1.00× ≥ 0.90×

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

Cross-version (7.0 vs 6.0)

Same reverse matrix measured on Titanium 7.0 versus committed 6.0 baselines (33087088466). Median of 3 @ be4d4e89. Source: Windows 36995934758, Linux 36995938517. Sustain RPS @ c=64. Absolute 7.0÷6.0 is 0.92–1.38× (Windows H3 is the high end). Peer-normalized TWP÷YARP is 0.94–1.17×.

Arm Win 6.0 Win 7.0 Win÷ Linux 6.0 Linux 7.0 Lin÷
twp-reverse-http1 32525 29929 0.92× 32755 31847 0.97×
twp-reverse-http1-tls 26461 25778 0.97× 22784 23663 1.04×
twp-reverse-http2 76038 87371 1.15× 47690 53242 1.12×
twp-reverse-http2-cleartext 40347 39304 0.97× 34556 34483 1.00×
twp-reverse-http3 17529 24213 1.38× 19468 20398 1.05×
twp-reverse-http3-cleartext 19956 19143 0.96× 20395 20825 1.02×
yarp-reverse-http1 (peer) 27504 26807 0.97× 27713 28228 1.02×
yarp-reverse-http2 (peer) 35553 34854 0.98× 29111 28944 0.99×

Both OS CSVs passed the cross-version check for this run. MITM arms are measured with the product reverse matrix, not this reverse-only comparison.

Heavier reverse workloads

Same runners and harness as the tiny-GET tables, but with larger bodies, POST, lossy links, TLS cost, and architecture-sensitive paths (slow consumer / early response / duplex). 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. Laptop numbers are on Performance Local Lab. How maintainers refresh these tables (modes, shards, paste scripts) is under Maintainer notes. Larger-body check: 64 / 256 KiB H2 TLS→H2 TLS is where body copy dominates headers — ratios vs YARP are in the tables below (@ 41f4adee), unlike the tiny-GET H2↔H2 medals above.

Lossy link = userspace delay/drop shim (not kernel netem): TCP gets per-buffer delay + occasional whole-connection stalls (honest head-of-line for multiplexed HTTP/2); UDP is loss% only (no per-datagram delay) + drops (QUIC / MsQuic-safe). Lossy tables publish HTTP/1, HTTP/2, and HTTP/3.

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

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

H1-client rows in this table were re-measured @ 1632b059 (37087735325); the other rows stay at the SHA above.

Not possible: HAProxy and Envoy columns are omitted (no official Windows port).

Body Client Origin TWP nginx YARP
64 KiB HTTP/1 · TLS HTTP/1 · plain 🥇 9,664
(93 MiB / 46.4% CPU)
641
(142 MiB / 24.7% CPU)
8,510
(132 MiB / 49.4% CPU)
64 KiB HTTP/2 · TLS HTTP/1 · plain 🥇 11,535
(174 MiB / 46.7% CPU)
814
(141 MiB / 24.8% CPU)
9,117
(134 MiB / 49.1% CPU)
64 KiB HTTP/3 · QUIC HTTP/1 · plain 🥇 3,937
(138 MiB / 37.4% CPU)
Not possible (no QUIC) 3,576
(196 MiB / 48.7% CPU)
256 KiB HTTP/1 · TLS HTTP/1 · plain 🥇 2,947
(126 MiB / 47.1% CPU)
167
(142 MiB / 24.7% CPU)
2,703
(135 MiB / 49.3% CPU)
256 KiB HTTP/2 · TLS HTTP/1 · plain 🥇 3,307
(152 MiB / 39.9% CPU)
197
(142 MiB / 24.7% CPU)
2,344
(134 MiB / 42.8% CPU)
256 KiB HTTP/3 · QUIC HTTP/1 · plain 1,076
(109 MiB / 39.1% CPU)
Not possible (no QUIC) 🥇 1,086
(165 MiB / 45.0% CPU)
64 KiB HTTP/2 · plain HTTP/1 · plain 🥇 13,884
(175 MiB / 39.7% CPU)
3,252
(127 MiB / 24.8% CPU)
13,150
(111 MiB / 44.4% CPU)
64 KiB HTTP/2 · TLS HTTP/2 · plain 🥇 9,634
(114 MiB / 38.6% CPU)
Not possible 6,556
(138 MiB / 49.3% CPU)
64 KiB HTTP/2 · TLS HTTP/2 · TLS 🥇 10,247
(120 MiB / 38.7% CPU)
Not possible 7,632
(131 MiB / 48.6% CPU)
64 KiB HTTP/3 · QUIC HTTP/2 · TLS 🥇 3,899
(124 MiB / 39.4% CPU)
Not possible 3,497
(187 MiB / 46.7% CPU)
64 KiB HTTP/3 · QUIC HTTP/1 · TLS 🥇 5,698
(143 MiB / 42.4% CPU)
Not possible (no QUIC) 4,442
(202 MiB / 48.0% CPU)
256 KiB HTTP/2 · plain HTTP/1 · plain 🥇 4,390
(145 MiB / 30.5% CPU)
978
(127 MiB / 24.5% CPU)
3,435
(123 MiB / 35.5% CPU)
256 KiB HTTP/2 · TLS HTTP/2 · plain 🥇 2,521
(142 MiB / 36.1% CPU)
Not possible 1,810
(167 MiB / 46.0% CPU)
256 KiB HTTP/2 · TLS HTTP/2 · TLS 🥇 2,262
(139 MiB / 36.4% CPU)
Not possible 1,776
(148 MiB / 42.8% CPU)
256 KiB HTTP/3 · QUIC HTTP/2 · TLS 880
(149 MiB / 42.4% CPU)
Not possible 🥇 995
(198 MiB / 43.8% CPU)
256 KiB HTTP/3 · QUIC HTTP/1 · TLS 🥇 1,389
(151 MiB / 39.5% CPU)
Not possible (no QUIC) 1,205
(203 MiB / 46.2% CPU)

nginx/Windows collapses on large reverse bodies in this harness; treat as same-OS only. H1 TLS 64 KiB ≈ 1.14× YARP; 256 KiB ≈ 1.09× (@ 1632b059). H2→H1 64 KiB ≈ 1.17×; H3→H1 64 KiB ≈ 1.06× (still @ 41f4adee).

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

Median of 3 repeats @ 41f4adee. Source: Actions 36853268072 (compare-bodies). Warmup 2s / measure 8s.

H1-client rows in this table were re-measured @ 1632b059 (37087735325); the other rows stay at the SHA above.

Body Client Origin TWP nginx HAProxy Envoy YARP
64 KiB HTTP/1 · TLS HTTP/1 · plain 9,734
(126 MiB / 43.8% CPU)
5,564
(100 MiB / 46.8% CPU)
🥇 9,967
(84 MiB / 40.2% CPU)
8,546
(132 MiB / 42.8% CPU)
7,390
(165 MiB / 47.8% CPU)
64 KiB HTTP/2 · TLS HTTP/1 · plain 🥇 13,272
(242 MiB / 39.0% CPU)
3,473
(102 MiB / 14.4% CPU)
11,621
(86 MiB / 24.2% CPU)
10,632
(141 MiB / 23.7% CPU)
11,543
(155 MiB / 44.1% CPU)
64 KiB HTTP/3 · QUIC HTTP/1 · plain 🥇 5,644
(184 MiB / 44.8% CPU)
1,651
(peak 1,696 · 105 MiB / 22.4% CPU)
4,139
(89 MiB / 27.6% CPU)
0
(138 MiB / 0.2% CPU)
4,274
(225 MiB / 51.4% CPU)
256 KiB HTTP/1 · TLS HTTP/1 · plain 2,843
(130 MiB / 35.2% CPU)
1,886
(99 MiB / 51.9% CPU)
🥇 3,084
(84 MiB / 32.9% CPU)
2,802
(146 MiB / 31.8% CPU)
2,306
(171 MiB / 43.5% CPU)
256 KiB HTTP/2 · TLS HTTP/1 · plain 2,976
(223 MiB / 28.9% CPU)
1,038
(102 MiB / 14.1% CPU)
🥇 3,705
(83 MiB / 18.9% CPU)
3,680
(162 MiB / 19.6% CPU)
3,086
(159 MiB / 37.0% CPU)
256 KiB HTTP/3 · QUIC HTTP/1 · plain 🥇 1,464
(157 MiB / 43.0% CPU)
448
(109 MiB / 23.9% CPU)
1,285
(89 MiB / 31.2% CPU)
805
(154 MiB / 20.2% CPU)
1,263
(217 MiB / 47.9% CPU)
64 KiB HTTP/2 · plain HTTP/1 · plain 🥇 19,850
(244 MiB / 41.2% CPU)
4,660
(80 MiB / 14.4% CPU)
14,246
(69 MiB / 24.2% CPU)
16,092
(130 MiB / 23.6% CPU)
16,776
(139 MiB / 40.5% CPU)
64 KiB HTTP/2 · TLS HTTP/2 · plain 🥇 5,497
(158 MiB / 34.4% CPU)
Not possible 2,086
(89 MiB / 24.6% CPU)
4,607
(144 MiB / 22.0% CPU)
4,582
(182 MiB / 46.7% CPU)
64 KiB HTTP/2 · TLS HTTP/2 · TLS 🥇 9,858
(184 MiB / 38.2% CPU)
Not possible 4,172
(82 MiB / 24.6% CPU)
7,131
(146 MiB / 22.6% CPU)
8,403
(186 MiB / 43.4% CPU)
64 KiB HTTP/3 · QUIC HTTP/2 · TLS 🥇 3,737
(171 MiB / 45.1% CPU)
Not possible 2,193
(92 MiB / 35.0% CPU)
12
(146 MiB / 0.2% CPU)
3,051
(237 MiB / 48.6% CPU)
64 KiB HTTP/3 · QUIC HTTP/1 · TLS 🥇 8,245
(219 MiB / 41.4% CPU)
2,792
(109 MiB / 23.1% CPU)
7,599
(97 MiB / 34.2% CPU)
0
(141 MiB / 0.1% CPU)
7,119
(247 MiB / 48.1% CPU)
256 KiB HTTP/2 · plain HTTP/1 · plain 4,671
(221 MiB / 30.6% CPU)
1,537
(80 MiB / 15.7% CPU)
4,782
(70 MiB / 18.6% CPU)
🥇 4,868
(157 MiB / 20.2% CPU)
4,754
(156 MiB / 31.8% CPU)
256 KiB HTTP/2 · TLS HTTP/2 · plain 🥇 1,766
(176 MiB / 32.2% CPU)
Not possible 590
(91 MiB / 24.4% CPU)
1,578
(171 MiB / 21.5% CPU)
1,296
(191 MiB / 42.6% CPU)
256 KiB HTTP/2 · TLS HTTP/2 · TLS 🥇 2,642
(169 MiB / 32.6% CPU)
Not possible 1,184
(88 MiB / 24.3% CPU)
1,995
(162 MiB / 22.5% CPU)
2,328
(185 MiB / 38.8% CPU)
256 KiB HTTP/3 · QUIC HTTP/2 · TLS 832
(188 MiB / 51.0% CPU)
Not possible 629
(94 MiB / 36.7% CPU)
651
(159 MiB / 22.3% CPU)
🥇 906
(225 MiB / 46.3% CPU)
256 KiB HTTP/3 · QUIC HTTP/1 · TLS 🥇 2,299
(192 MiB / 40.3% CPU)
719
(123 MiB / 23.7% CPU)
1,886
(96 MiB / 29.9% CPU)
1,235
(143 MiB / 20.2% CPU)
1,924
(238 MiB / 44.8% CPU)

On this GHA pass TWP÷YARP H1 TLS ≈ 1.32× (64 KiB) / 1.23× (256 KiB) @ 1632b059; H2→H1 ≈ 1.20× / 1.15×; H3→H1 ≈ 1.29× / 1.16× (still @ 41f4adee). TWP÷nginx H1 TLS ≈ 1.75 / 1.51. Absolute RPS swings by VM; prefer ratios.

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

Median of 3 repeats on macos-15 (Apple Silicon M1, 3-core / 7 GB) @ 41f4adee. Source: Actions 36998170689 (compare-bodies). Warmup 2s / measure 8s.

H1-client rows in this table were re-measured @ 1632b059 (37087735325); the other rows stay at the SHA above.

Body Client Origin TWP nginx HAProxy Envoy YARP
64 KiB HTTP/1 · TLS HTTP/1 · plain 3,356
(188 MiB / 19.0% CPU)
🥇 7,050
(84 MiB / 21.3% CPU)
5,495
(101 MiB / 30.4% CPU)
5,366
(135 MiB / 28.6% CPU)
3,746
(181 MiB / 25.3% CPU)
64 KiB HTTP/2 · TLS HTTP/1 · plain 3,190
(432 MiB / 25.3% CPU)
5,651
(85 MiB / 24.4% CPU)
4,656
(103 MiB / 20.4% CPU)
3,817
(145 MiB / 18.1% CPU)
🥇 5,858
(169 MiB / 44.3% CPU)
64 KiB HTTP/3 · QUIC HTTP/1 · plain 🥇 2,322
(281 MiB / 33.9% CPU)
0
(peak 2,645 · 106 MiB / 28.5% CPU)
2,009
(103 MiB / 29.8% CPU)
1,329
(138 MiB / 31.6% CPU)
2,001
(423 MiB / 32.9% CPU)
256 KiB HTTP/1 · TLS HTTP/1 · plain 2,310
(193 MiB / 24.8% CPU)
🥇 2,744
(84 MiB / 20.5% CPU)
2,601
(101 MiB / 30.1% CPU)
2,161
(154 MiB / 25.3% CPU)
1,722
(183 MiB / 33.2% CPU)
256 KiB HTTP/2 · TLS HTTP/1 · plain 1,620
(400 MiB / 35.5% CPU)
1,930
(85 MiB / 23.7% CPU)
🥇 2,510
(101 MiB / 23.9% CPU)
2,147
(155 MiB / 25.9% CPU)
1,477
(181 MiB / 43.1% CPU)
256 KiB HTTP/3 · QUIC HTTP/1 · plain 592
(174 MiB / 30.8% CPU)
🥇 782
(peak 957 · 82 MiB / 28.2% CPU)
484
(100 MiB / 31.8% CPU)
277
(160 MiB / 35.6% CPU)
290
(469 MiB / 31.8% CPU)
64 KiB HTTP/2 · plain HTTP/1 · plain 4,044
(443 MiB / 15.8% CPU)
9,200
(73 MiB / 23.0% CPU)
10,775
(89 MiB / 23.9% CPU)
🥇 13,514
(135 MiB / 27.6% CPU)
11,451
(181 MiB / 37.0% CPU)
64 KiB HTTP/2 · TLS HTTP/2 · plain 6,719
(304 MiB / 32.0% CPU)
Not possible 3,562
(104 MiB / 24.1% CPU)
6,814
(131 MiB / 27.9% CPU)
🥇 7,703
(244 MiB / 43.3% CPU)
64 KiB HTTP/2 · TLS HTTP/2 · TLS 5,719
(209 MiB / 33.7% CPU)
Not possible 3,312
(101 MiB / 24.9% CPU)
🥇 5,834
(131 MiB / 28.5% CPU)
4,946
(236 MiB / 42.3% CPU)
64 KiB HTTP/3 · QUIC HTTP/2 · TLS 🥇 1,893
(238 MiB / 32.0% CPU)
Not possible 1,328
(103 MiB / 31.9% CPU)
700
(129 MiB / 39.1% CPU)
1,795
(432 MiB / 33.8% CPU)
64 KiB HTTP/3 · QUIC HTTP/1 · TLS 🥇 2,180
(247 MiB / 42.0% CPU)
0
(peak 1,559 · 112 MiB / 24.6% CPU)
1,788
(104 MiB / 34.2% CPU)
1,286
(139 MiB / 39.2% CPU)
1,291
(389 MiB / 40.4% CPU)
256 KiB HTTP/2 · plain HTTP/1 · plain 3,184
(370 MiB / 29.6% CPU)
3,310
(73 MiB / 19.6% CPU)
🥇 3,514
(87 MiB / 21.9% CPU)
3,199
(146 MiB / 24.7% CPU)
2,653
(192 MiB / 34.1% CPU)
256 KiB HTTP/2 · TLS HTTP/2 · plain 🥇 2,076
(705 MiB / 30.5% CPU)
Not possible 1,050
(112 MiB / 23.1% CPU)
2,021
(155 MiB / 27.1% CPU)
1,535
(330 MiB / 39.0% CPU)
256 KiB HTTP/2 · TLS HTTP/2 · TLS 🥇 1,313
(449 MiB / 32.4% CPU)
Not possible 807
(102 MiB / 24.3% CPU)
1,311
(155 MiB / 26.9% CPU)
1,017
(304 MiB / 38.5% CPU)
256 KiB HTTP/3 · QUIC HTTP/2 · TLS 112
(peak 168 · 279 MiB / 25.3% CPU)
Not possible 310
(peak 370 · 103 MiB / 30.3% CPU)
164
(126 MiB / 36.6% CPU)
🥇 495
(484 MiB / 40.4% CPU)
256 KiB HTTP/3 · QUIC HTTP/1 · TLS 🥇 630
(218 MiB / 39.4% CPU)
575
(105 MiB / 28.2% CPU)
506
(104 MiB / 33.6% CPU)
249
(125 MiB / 31.5% CPU)
504
(423 MiB / 43.1% CPU)

Windows — POST 64 KiB request + 64 KiB response

Median of 3 repeats on windows-latest @ 41f4adee. Source: Actions 36853276459 (compare-post).

H1-client rows in this table were re-measured @ 1632b059 (37087741549); the other rows stay at the SHA above.

Not possible: HAProxy and Envoy columns are omitted (no official Windows port).

Client Origin TWP nginx YARP
HTTP/1 · TLS HTTP/1 · plain 🥇 6,119
(93 MiB / 41.5% CPU)
369
(142 MiB / 24.8% CPU)
4,240
(138 MiB / 55.2% CPU)
HTTP/2 · TLS HTTP/1 · plain 🥇 4,572
(178 MiB / 48.4% CPU)
375
(144 MiB / 24.8% CPU)
3,938
(135 MiB / 51.0% CPU)
HTTP/3 · QUIC HTTP/1 · plain 🥇 2,239
(175 MiB / 40.5% CPU)
Not possible (no QUIC) 2,223
(218 MiB / 49.3% CPU)
HTTP/2 · plain HTTP/1 · plain 🥇 6,806
(178 MiB / 46.5% CPU)
1,946
(130 MiB / 24.8% CPU)
6,321
(124 MiB / 51.1% CPU)
HTTP/2 · TLS HTTP/2 · plain 🥇 5,685
(123 MiB / 38.1% CPU)
Not possible 3,894
(143 MiB / 48.9% CPU)
HTTP/2 · TLS HTTP/2 · TLS 🥇 4,157
(123 MiB / 38.9% CPU)
Not possible 3,060
(142 MiB / 47.3% CPU)
HTTP/3 · QUIC HTTP/2 · TLS 🥇 2,301
(164 MiB / 43.3% CPU)
Not possible 2,018
(206 MiB / 48.5% CPU)
HTTP/3 · QUIC HTTP/1 · TLS 🥇 2,104
(183 MiB / 41.8% CPU)
Not possible (no QUIC) 2,002
(215 MiB / 49.3% CPU)

TWP leads H1 POST (1.44× / 1.51× YARP on Windows and Linux, @ 1632b059), H2→H1 POST (1.1-1.3x YARP), and H3 POST (~1.0-1.1x YARP). H2 TLS→H2 TLS POST sustain is healthy on this pass (@ 41f4adee; TWP ~1.2-1.4x YARP).

Linux — POST 64 KiB request + 64 KiB response

Median of 3 repeats @ 41f4adee. Source: Actions 36853276459 (compare-post).

H1-client rows in this table were re-measured @ 1632b059 (37087741549); the other rows stay at the SHA above.

Client Origin TWP nginx HAProxy Envoy YARP
HTTP/1 · TLS HTTP/1 · plain 8,220
(133 MiB / 40.9% CPU)
6,252
(102 MiB / 43.0% CPU)
🥇 8,852
(85 MiB / 37.6% CPU)
8,457
(133 MiB / 41.6% CPU)
5,446
(175 MiB / 53.3% CPU)
HTTP/2 · TLS HTTP/1 · plain 🥇 3,056
(217 MiB / 47.3% CPU)
1,354
(112 MiB / 21.0% CPU)
1,818
(82 MiB / 23.9% CPU)
0
(peak 2,979 · 143 MiB / 23.9% CPU)
2,533
(168 MiB / 48.1% CPU)
HTTP/3 · QUIC HTTP/1 · plain 🥇 2,878
(220 MiB / 44.2% CPU)
463
(109 MiB / 24.9% CPU)
1,991
(90 MiB / 33.1% CPU)
0
(126 MiB / 0.1% CPU)
2,593
(243 MiB / 49.2% CPU)
HTTP/2 · plain HTTP/1 · plain 🥇 5,628
(228 MiB / 44.0% CPU)
2,396
(96 MiB / 24.1% CPU)
2,567
(68 MiB / 23.2% CPU)
4,030
(peak 4,604 · 118 MiB / 21.6% CPU)
4,340
(160 MiB / 47.3% CPU)
HTTP/2 · TLS HTTP/2 · plain 🥇 3,268
(159 MiB / 41.6% CPU)
Not possible 1,340
(86 MiB / 24.0% CPU)
2,854
(142 MiB / 23.5% CPU)
2,389
(179 MiB / 46.1% CPU)
HTTP/2 · TLS HTTP/2 · TLS 🥇 2,330
(148 MiB / 40.5% CPU)
Not possible 1,010
(80 MiB / 23.1% CPU)
2,062
(146 MiB / 22.9% CPU)
1,903
(170 MiB / 46.3% CPU)
HTTP/3 · QUIC HTTP/2 · TLS 🥇 2,132
(222 MiB / 43.9% CPU)
Not possible 1,136
(96 MiB / 31.5% CPU)
0
(126 MiB / 0.1% CPU)
1,800
(250 MiB / 47.3% CPU)
HTTP/3 · QUIC HTTP/1 · TLS 🥇 2,139
(232 MiB / 44.8% CPU)
368
(120 MiB / 25.0% CPU)
1,669
(96 MiB / 36.1% CPU)
0
(125 MiB / 0.1% CPU)
1,973
(255 MiB / 48.6% CPU)

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

macOS — POST 64 KiB request + 64 KiB response

Median of 3 repeats on macos-15 (Apple Silicon M1, 3-core / 7 GB) @ 41f4adee. Source: Actions 37006733365 (compare-post).

H1-client rows in this table were re-measured @ 1632b059 (37087741549); the other rows stay at the SHA above.

Client Origin TWP nginx HAProxy Envoy YARP
HTTP/1 · TLS HTTP/1 · plain 4,407
(235 MiB / 34.1% CPU)
4,196
(90 MiB / 25.7% CPU)
🥇 5,993
(102 MiB / 34.4% CPU)
4,425
(134 MiB / 36.2% CPU)
2,840
(199 MiB / 44.8% CPU)
HTTP/2 · TLS HTTP/1 · plain 2,377
(419 MiB / 36.4% CPU)
🥇 2,705
(97 MiB / 21.8% CPU)
2,257
(101 MiB / 24.2% CPU)
0
(peak 2,840 · 146 MiB / 27.0% CPU)
2,266
(182 MiB / 41.1% CPU)
HTTP/3 · QUIC HTTP/1 · plain 🥇 918
(371 MiB / 31.7% CPU)
0
(peak 1,042 · 86 MiB / 26.5% CPU)
862
(101 MiB / 32.1% CPU)
311
(122 MiB / 38.2% CPU)
735
(447 MiB / 29.2% CPU)
HTTP/2 · plain HTTP/1 · plain 4,651
(388 MiB / 31.7% CPU)
🥇 4,975
(85 MiB / 21.3% CPU)
3,694
(86 MiB / 23.4% CPU)
0
(peak 4,705 · 135 MiB / 25.7% CPU)
3,661
(189 MiB / 41.0% CPU)
HTTP/2 · TLS HTTP/2 · plain 3,798
(317 MiB / 30.6% CPU)
Not possible 2,251
(102 MiB / 24.1% CPU)
🥇 4,595
(134 MiB / 26.9% CPU)
3,220
(203 MiB / 45.5% CPU)
HTTP/2 · TLS HTTP/2 · TLS 2,110
(245 MiB / 33.3% CPU)
Not possible 1,592
(103 MiB / 25.1% CPU)
🥇 2,156
(133 MiB / 26.5% CPU)
1,876
(209 MiB / 40.6% CPU)
HTTP/3 · QUIC HTTP/2 · TLS 🥇 1,233
(263 MiB / 39.1% CPU)
Not possible 647
(103 MiB / 32.6% CPU)
429
(peak 477 · 129 MiB / 39.7% CPU)
925
(400 MiB / 41.4% CPU)
HTTP/3 · QUIC HTTP/1 · TLS 🥇 1,035
(278 MiB / 39.5% CPU)
313
(peak 779 · 83 MiB / 12.8% CPU)
663
(99 MiB / 31.0% CPU)
309
(peak 409 · 130 MiB / 32.1% CPU)
828
(435 MiB / 37.6% CPU)

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

Userspace 5 ms one-way delay + 1% TCP connection stall (H1/H2); UDP is loss% only (no per-datagram delay; MsQuic-safe) + 1% datagram drop (H3); 64 KiB GET. Median of 3 repeats on windows-latest @ 41f4adee — 36853280515 (compare-lossy).

H1-client rows in this table were re-measured @ 1632b059 (37087746860); the other rows stay at the SHA above.

Not possible: HAProxy and Envoy columns are omitted (no official Windows port).

Client Origin TWP nginx YARP
HTTP/1 · TLS HTTP/1 · plain 662
(87 MiB / 4.2% CPU)
636
(142 MiB / 19.1% CPU)
🥇 665
(122 MiB / 5.2% CPU)
HTTP/2 · TLS HTTP/1 · plain 🥇 59
(peak 85 · 112 MiB / 1.6% CPU)
18
(142 MiB / 0.6% CPU)
18
(86 MiB / 0.9% CPU)
HTTP/3 · QUIC HTTP/1 · plain 🥇 1,123
(124 MiB / 16.9% CPU)
Not possible (no QUIC) 969
(188 MiB / 22.0% CPU)
HTTP/2 · plain HTTP/1 · plain 🥇 61
(peak 87 · 106 MiB / 1.6% CPU)
18
(127 MiB / 0.1% CPU)
17
(78 MiB / 0.9% CPU)
HTTP/2 · TLS HTTP/2 · plain 16
(79 MiB / 0.9% CPU)
Not possible 🥇 18
(85 MiB / 1.4% CPU)
HTTP/2 · TLS HTTP/2 · TLS 16
(78 MiB / 0.7% CPU)
Not possible 🥇 18
(84 MiB / 1.4% CPU)
HTTP/3 · QUIC HTTP/2 · TLS 1,042
(121 MiB / 16.1% CPU)
Not possible 🥇 1,244
(192 MiB / 29.5% CPU)
HTTP/3 · QUIC HTTP/1 · TLS 998
(132 MiB / 16.4% CPU)
Not possible (no QUIC) 🥇 1,217
(185 MiB / 30.3% CPU)

H1 is near parity with YARP. H2 HOL and H3 loss sustain are non-zero on this Windows GHA pass (@ 41f4adee); see table.

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

Median of 3 repeats @ 41f4adee. Source: 36853280515 (compare-lossy; lossy H3 uses quic-http3, UDP drop-only).

H1-client rows in this table were re-measured @ 1632b059 (37087746860); the other rows stay at the SHA above.

Client Origin TWP nginx HAProxy Envoy YARP
HTTP/1 · TLS HTTP/1 · plain 1,203
(120 MiB / 13.2% CPU)
1,210
(100 MiB / 11.9% CPU)
🥇 1,210
(82 MiB / 7.2% CPU)
1,201
(127 MiB / 9.0% CPU)
1,198
(154 MiB / 17.1% CPU)
HTTP/2 · TLS HTTP/1 · plain 🥇 315
(177 MiB / 5.2% CPU)
40
(101 MiB / 0.2% CPU)
40
(84 MiB / 0.2% CPU)
40
(137 MiB / 0.3% CPU)
40
(127 MiB / 1.1% CPU)
HTTP/3 · QUIC HTTP/1 · plain 1,516
(165 MiB / 21.2% CPU)
🥇 1,570
(117 MiB / 23.2% CPU)
1,338
(87 MiB / 18.7% CPU)
1,462
(138 MiB / 24.5% CPU)
1,510
(228 MiB / 30.5% CPU)
HTTP/2 · plain HTTP/1 · plain 🥇 329
(185 MiB / 5.3% CPU)
40
(78 MiB / 0.2% CPU)
41
(69 MiB / 0.1% CPU)
40
(127 MiB / 0.3% CPU)
40
(123 MiB / 0.9% CPU)
HTTP/2 · TLS HTTP/2 · plain 40
(111 MiB / 0.7% CPU)
Not possible 40
(85 MiB / 0.3% CPU)
40
(135 MiB / 0.3% CPU)
🥇 41
(130 MiB / 1.3% CPU)
HTTP/2 · TLS HTTP/2 · TLS 40
(108 MiB / 0.9% CPU)
Not possible 🥇 42
(87 MiB / 0.3% CPU)
40
(137 MiB / 0.3% CPU)
41
(130 MiB / 1.2% CPU)
HTTP/3 · QUIC HTTP/2 · TLS 🥇 1,477
(163 MiB / 23.1% CPU)
Not possible 1,212
(93 MiB / 21.4% CPU)
1,379
(140 MiB / 25.8% CPU)
1,417
(235 MiB / 31.4% CPU)
HTTP/3 · QUIC HTTP/1 · TLS 1,351
(169 MiB / 21.9% CPU)
🥇 1,452
(120 MiB / 23.7% CPU)
1,289
(92 MiB / 20.8% CPU)
1,404
(138 MiB / 25.9% CPU)
1,384
(213 MiB / 30.8% CPU)

TWP H2 HOL ≫ YARP (~7.8×). H3 TWP÷YARP ≈ 1.07×.

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

Median of 3 repeats on macos-15 (Apple Silicon M1, 3-core / 7 GB) @ 41f4adee. Source: 37007354328 (compare-lossy; lossy H3 uses quic-http3, UDP drop-only).

H1-client rows in this table were re-measured @ 1632b059 (37087746860); the other rows stay at the SHA above.

Client Origin TWP nginx HAProxy Envoy YARP
HTTP/1 · TLS HTTP/1 · plain 411
(250 MiB / 3.7% CPU)
393
(84 MiB / 1.2% CPU)
🥇 500
(101 MiB / 2.6% CPU)
437
(132 MiB / 2.4% CPU)
328
(221 MiB / 3.0% CPU)
HTTP/2 · TLS HTTP/1 · plain 🥇 24
(peak 45 · 140 MiB / 3.6% CPU)
12
(81 MiB / 0.2% CPU)
10
(96 MiB / 0.2% CPU)
8
(118 MiB / 0.3% CPU)
11
(130 MiB / 3.3% CPU)
HTTP/3 · QUIC HTTP/1 · plain 1,103
(279 MiB / 25.6% CPU)
🥇 1,532
(98 MiB / 15.9% CPU)
673
(101 MiB / 21.7% CPU)
1,023
(131 MiB / 29.9% CPU)
557
(429 MiB / 21.6% CPU)
HTTP/2 · plain HTTP/1 · plain 🥇 19
(peak 46 · 119 MiB / 2.4% CPU)
12
(69 MiB / 0.1% CPU)
12
(83 MiB / 0.2% CPU)
10
(108 MiB / 0.3% CPU)
12
(117 MiB / 2.5% CPU)
HTTP/2 · TLS HTTP/2 · plain 🥇 12
(114 MiB / 2.2% CPU)
Not possible 11
(97 MiB / 0.3% CPU)
8
(118 MiB / 0.3% CPU)
11
(128 MiB / 3.2% CPU)
HTTP/2 · TLS HTTP/2 · TLS 10
(125 MiB / 2.3% CPU)
Not possible 10
(peak 12 · 98 MiB / 0.4% CPU)
8
(118 MiB / 0.3% CPU)
🥇 11
(131 MiB / 3.6% CPU)
HTTP/3 · QUIC HTTP/2 · TLS 906
(194 MiB / 24.3% CPU)
Not possible 840
(105 MiB / 22.4% CPU)
🥇 1,154
(131 MiB / 31.1% CPU)
1,016
(444 MiB / 31.0% CPU)
HTTP/3 · QUIC HTTP/1 · TLS 1,346
(195 MiB / 27.6% CPU)
🥇 1,437
(101 MiB / 18.0% CPU)
872
(106 MiB / 22.9% CPU)
946
(131 MiB / 35.0% CPU)
878
(389 MiB / 31.5% CPU)

Architecture-sensitive

These runs isolate slow app readers, origin-early response, HTTP/2 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 @ 41f4adee (36853288521). Slow consumer = 256 KiB GET, 16 KiB read + 8 ms sleep. Early response = 64 KiB POST, origin writes after 8 KiB. Duplex HTTP/2 = overlapping 64 KiB POST on H2 TLS↔H2 TLS. WebSocket row = H1 Upgrade echo round-trips/sec (not RFC 8441; see WebSocket RFC 8441 table below).

Lossy-link runs (slow network) are already published above; they are not a slow app reader.

Windows

H1-client rows in this table were re-measured @ 1632b059 (37087752181); the other rows stay at the SHA above.

Not possible: HAProxy and Envoy columns are omitted (no official Windows port).

Scenario Client Origin TWP nginx YARP
Slow consumer (256 KiB GET, throttled client read) HTTP/1 · TLS HTTP/1 · plain 🥇 256
(93 MiB / 1.8% CPU)
243
(145 MiB / 15.8% CPU)
255
(110 MiB / 3.2% CPU)
Slow consumer (256 KiB GET, throttled client read) HTTP/2 · TLS HTTP/1 · plain 256
(118 MiB / 3.6% CPU)
230
(141 MiB / 24.4% CPU)
🥇 256
(114 MiB / 5.3% CPU)
Slow consumer (256 KiB GET, throttled client read) HTTP/3 · QUIC HTTP/1 · plain 🥇 282
(104 MiB / 12.3% CPU)
Not possible (no QUIC) 276
(173 MiB / 14.6% CPU)
Early response (origin writes after first request chunk) HTTP/1 · TLS HTTP/1 · plain 🥇 12,591
(95 MiB / 41.7% CPU)
699
(142 MiB / 24.8% CPU)
8,321
(135 MiB / 53.4% CPU)
Early response (origin writes after first request chunk) HTTP/2 · TLS HTTP/1 · plain 🥇 3,367
(202 MiB / 52.7% CPU)
350
(143 MiB / 24.7% CPU)
2,711
(136 MiB / 49.8% CPU)
Early response (origin writes after first request chunk) HTTP/3 · QUIC HTTP/1 · plain 🥇 2,777
(153 MiB / 42.6% CPU)
Not possible (no QUIC) 2,477
(213 MiB / 52.8% CPU)
Slow consumer (256 KiB GET, throttled client read) HTTP/2 · plain HTTP/1 · plain 248
(108 MiB / 3.2% CPU)
250
(127 MiB / 9.2% CPU)
🥇 256
(105 MiB / 4.1% CPU)
Slow consumer (256 KiB GET, throttled client read) HTTP/2 · TLS HTTP/2 · TLS 🥇 256
(123 MiB / 3.5% CPU)
Not possible 256
(142 MiB / 6.1% CPU)
Early response (origin writes after first request chunk) HTTP/2 · TLS HTTP/2 · TLS 🥇 3,606
(118 MiB / 30.4% CPU)
Not possible 21
(103 MiB / 0.3% CPU)
Duplex (both directions live) HTTP/2 · TLS HTTP/2 · TLS 🥇 3,531
(119 MiB / 29.9% CPU)
Not possible 15
(109 MiB / 0.3% CPU)
Duplex (WebSocket / H1 Upgrade) HTTP/1 · TLS HTTP/1 · plain 🥇 65,462
(98 MiB / 43.5% CPU)
36,780
(143 MiB / 24.9% CPU)
64,803
(90 MiB / 46.3% CPU)

Linux

H1-client rows in this table were re-measured @ 1632b059 (37087752181); the other rows stay at the SHA above.

Scenario Client Origin TWP nginx HAProxy Envoy YARP
Slow consumer (256 KiB GET, throttled client read) HTTP/1 · TLS HTTP/1 · plain 463
(117 MiB / 9.3% CPU)
416
(100 MiB / 9.8% CPU)
🥇 468
(85 MiB / 5.9% CPU)
467
(137 MiB / 6.2% CPU)
414
(141 MiB / 13.8% CPU)
Slow consumer (256 KiB GET, throttled client read) HTTP/2 · TLS HTTP/1 · plain 🥇 474
(137 MiB / 19.9% CPU)
468
(100 MiB / 23.1% CPU)
473
(84 MiB / 9.3% CPU)
468
(154 MiB / 7.5% CPU)
466
(144 MiB / 23.9% CPU)
Slow consumer (256 KiB GET, throttled client read) HTTP/3 · QUIC HTTP/1 · plain 🥇 473
(130 MiB / 34.8% CPU)
120
(peak 342 · 94 MiB / 5.8% CPU)
472
(88 MiB / 10.5% CPU)
1
(144 MiB / 0.2% CPU)
472
(195 MiB / 37.8% CPU)
Early response (origin writes after first request chunk) HTTP/1 · TLS HTTP/1 · plain 4,744
(137 MiB / 47.5% CPU)
3,530
(peak 3,554 · 99 MiB / 50.5% CPU)
🥇 4,921
(84 MiB / 43.2% CPU)
0
(peak 2,789 · 129 MiB / 25.6% CPU)
3,176
(178 MiB / 56.5% CPU)
Early response (origin writes after first request chunk) HTTP/2 · TLS HTTP/1 · plain 🥇 2,430
(218 MiB / 48.5% CPU)
0
(peak 1,345 · 112 MiB / 24.1% CPU)
1,363
(84 MiB / 12.8% CPU)
0
(peak 2,354 · 153 MiB / 21.4% CPU)
2,167
(168 MiB / 48.6% CPU)
Early response (origin writes after first request chunk) HTTP/3 · QUIC HTTP/1 · plain 🥇 2,939
(193 MiB / 45.7% CPU)
0
(peak 442 · 116 MiB / 24.8% CPU)
1,956
(91 MiB / 34.3% CPU)
0
(126 MiB / 0.1% CPU)
2,141
(253 MiB / 48.4% CPU)
Slow consumer (256 KiB GET, throttled client read) HTTP/2 · plain HTTP/1 · plain 478
(142 MiB / 16.7% CPU)
463
(79 MiB / 11.7% CPU)
🥇 480
(68 MiB / 6.7% CPU)
474
(146 MiB / 4.7% CPU)
468
(147 MiB / 15.5% CPU)
Slow consumer (256 KiB GET, throttled client read) HTTP/2 · TLS HTTP/2 · TLS 452
(144 MiB / 21.1% CPU)
Not possible 461
(88 MiB / 19.4% CPU)
460
(152 MiB / 12.4% CPU)
🥇 470
(162 MiB / 29.9% CPU)
Early response (origin writes after first request chunk) HTTP/2 · TLS HTTP/2 · TLS 🥇 1,936
(peak 2,226 · 141 MiB / 35.4% CPU)
Not possible 1,147
(84 MiB / 23.2% CPU)
0
(peak 2,192 · 151 MiB / 21.8% CPU)
50
(145 MiB / 1.4% CPU)
Duplex (both directions live) HTTP/2 · TLS HTTP/2 · TLS 🥇 1,934
(141 MiB / 35.7% CPU)
Not possible 256
(86 MiB / 4.8% CPU)
0
(peak 2,083 · 156 MiB / 21.4% CPU)
6
(150 MiB / 0.4% CPU)
Duplex (WebSocket / H1 Upgrade) HTTP/1 · TLS HTTP/1 · plain 31,180
(125 MiB / 43.6% CPU)
🥇 33,544
(99 MiB / 35.4% CPU)
31,272
(82 MiB / 39.2% CPU)
31,344
(127 MiB / 39.6% CPU)
27,547
(125 MiB / 44.3% CPU)

macOS

Median of 3 repeats on macos-15 (Apple Silicon M1, 3-core / 7 GB) @ 41f4adee. Source: Actions 37000022693 (compare-arch).

H1-client rows in this table were re-measured @ 1632b059 (37087752181); the other rows stay at the SHA above.

Scenario Client Origin TWP nginx HAProxy Envoy YARP
Slow consumer (256 KiB GET, throttled client read) HTTP/1 · TLS HTTP/1 · plain 124
(239 MiB / 5.6% CPU)
🥇 130
(85 MiB / 2.7% CPU)
120
(101 MiB / 2.6% CPU)
104
(144 MiB / 2.5% CPU)
115
(219 MiB / 4.6% CPU)
Slow consumer (256 KiB GET, throttled client read) HTTP/2 · TLS HTTP/1 · plain 103
(311 MiB / 5.5% CPU)
95
(84 MiB / 1.3% CPU)
🥇 120
(100 MiB / 1.9% CPU)
88
(155 MiB / 2.0% CPU)
112
(215 MiB / 5.5% CPU)
Slow consumer (256 KiB GET, throttled client read) HTTP/3 · QUIC HTTP/1 · plain 🥇 155
(135 MiB / 18.8% CPU)
116
(94 MiB / 6.6% CPU)
113
(102 MiB / 10.0% CPU)
154
(159 MiB / 33.9% CPU)
144
(366 MiB / 20.1% CPU)
Early response (origin writes after first request chunk) HTTP/1 · TLS HTTP/1 · plain 4,072
(222 MiB / 33.5% CPU)
🥇 5,368
(89 MiB / 24.9% CPU)
4,475
(103 MiB / 31.9% CPU)
4,938
(133 MiB / 34.3% CPU)
3,289
(194 MiB / 45.2% CPU)
Early response (origin writes after first request chunk) HTTP/2 · TLS HTTP/1 · plain 1,324
(443 MiB / 27.6% CPU)
0
(peak 3,690 · 98 MiB / 22.5% CPU)
🥇 3,214
(103 MiB / 25.1% CPU)
0
(peak 2,363 · 147 MiB / 25.7% CPU)
2,337
(179 MiB / 42.3% CPU)
Early response (origin writes after first request chunk) HTTP/3 · QUIC HTTP/1 · plain 🥇 1,181
(277 MiB / 39.3% CPU)
0
(peak 1,423 · 112 MiB / 26.5% CPU)
0
(66 MiB)
390
(133 MiB / 34.0% CPU)
978
(450 MiB / 38.5% CPU)
Slow consumer (256 KiB GET, throttled client read) HTTP/2 · plain HTTP/1 · plain 112
(185 MiB / 5.6% CPU)
104
(72 MiB / 1.1% CPU)
🥇 112
(86 MiB / 1.9% CPU)
89
(145 MiB / 1.7% CPU)
96
(145 MiB / 2.8% CPU)
Slow consumer (256 KiB GET, throttled client read) HTTP/2 · TLS HTTP/2 · TLS 128
(306 MiB / 5.9% CPU)
Not possible 🥇 139
(105 MiB / 5.8% CPU)
104
(144 MiB / 3.8% CPU)
122
(286 MiB / 4.5% CPU)
Early response (origin writes after first request chunk) HTTP/2 · TLS HTTP/2 · TLS 🥇 1,925
(183 MiB / 27.5% CPU)
Not possible 0
(98 MiB)
0
(peak 2,622 · 146 MiB / 25.5% CPU)
125
(181 MiB / 1.3% CPU)
Duplex (both directions live) HTTP/2 · TLS HTTP/2 · TLS 🥇 1,922
(peak 2,353 · 179 MiB / 29.7% CPU)
Not possible 1,517
(98 MiB / 22.9% CPU)
0
(peak 1,960 · 119 MiB / 14.0% CPU)
5
(173 MiB / 0.1% CPU)
Duplex (WebSocket / H1 Upgrade) HTTP/1 · TLS HTTP/1 · plain 23,530
(156 MiB / 30.4% CPU)
28,582
(87 MiB / 20.4% CPU)
30,803
(100 MiB / 27.1% CPU)
🥇 33,344
(121 MiB / 31.8% CPU)
22,155
(185 MiB / 31.2% CPU)

Slow consumer is sleep-bound; H1/H2/H3 sit in the same band. Early-response H1: TWP is ~1.51× / 1.49× YARP Win/Linux @ 1632b059. Duplex H2 sustain is non-zero for TWP on the 41f4adee pass. WebSocket H1: TWP is ~1.01× YARP on Windows and ~1.13× on Linux @ 1632b059.

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

Median of 3 repeats on windows-latest @ 41f4adee. Source: Actions 36853284423. Absolute RPS on GHA swings hard; prefer TWP÷YARP.

H1-client rows in this table were re-measured @ 1632b059 (37087757165); the other rows stay at the SHA above.

Not possible: HAProxy and Envoy columns are omitted (no official Windows port).

Workload TWP nginx YARP
Keep-alive · tiny GET 🥇 20,934
(85 MiB / 50.2% CPU)
8,635
(143 MiB / 24.8% CPU)
18,070
(101 MiB / 50.2% CPU)
New-connection · tiny GET 🥇 738
(81 MiB / 0.4% CPU)
256
(141 MiB / 2.3% CPU)
721
(114 MiB / 0.4% CPU)
Keep-alive · 256 KiB GET 🥇 2,861
(128 MiB / 47.8% CPU)
166
(142 MiB / 24.9% CPU)
2,515
(136 MiB / 46.6% CPU)

Linux

Median of 3 repeats @ 41f4adee. Source: Actions 36853284423.

H1-client rows in this table were re-measured @ 1632b059 (37087757165); the other rows stay at the SHA above.

Workload TWP nginx HAProxy Envoy YARP
Keep-alive · tiny GET 24,706
(106 MiB / 49.6% CPU)
🥇 28,661
(100 MiB / 41.6% CPU)
28,142
(84 MiB / 43.1% CPU)
17,283
(128 MiB / 58.6% CPU)
20,758
(134 MiB / 51.1% CPU)
New-connection · tiny GET 974
(121 MiB / 0.3% CPU)
1,014
(99 MiB / 1.0% CPU)
958
(84 MiB / 0.2% CPU)
🥇 1,089
(129 MiB / 0.2% CPU)
970
(147 MiB / 0.2% CPU)
Keep-alive · 256 KiB GET 2,720
(129 MiB / 36.4% CPU)
1,725
(99 MiB / 53.2% CPU)
🥇 2,969
(83 MiB / 33.4% CPU)
2,680
(145 MiB / 33.9% CPU)
2,137
(168 MiB / 45.8% CPU)

macOS

Median of 3 repeats on macos-15 @ 41f4adee. Source: Actions 37008082576.

H1-client rows in this table were re-measured @ 1632b059 (37087757165); the other rows stay at the SHA above.

Workload TWP nginx HAProxy Envoy YARP
Keep-alive · tiny GET 24,835
(126 MiB / 34.9% CPU)
27,091
(85 MiB / 24.1% CPU)
🥇 30,691
(100 MiB / 32.4% CPU)
21,864
(121 MiB / 47.2% CPU)
27,331
(157 MiB / 38.7% CPU)
New-connection · tiny GET 113
(peak 131 · 107 MiB)
917
(87 MiB)
1,047
(101 MiB)
🥇 1,055
(131 MiB / 0.1% CPU)
154
(188 MiB / 0.7% CPU)
Keep-alive · 256 KiB GET 3,071
(181 MiB / 28.7% CPU)
2,634
(84 MiB / 24.4% CPU)
🥇 3,234
(101 MiB / 30.2% CPU)
3,046
(154 MiB / 28.5% CPU)
2,405
(182 MiB / 38.8% CPU)

On Windows, TWP vs YARP is ~1.16× keep-alive tiny, ~1.02× new-connection, ~1.14× keep-alive 256 KiB (@ 1632b059). On Linux, TWP vs YARP is ~1.19× keep-alive tiny, ~1.00× new-connection, ~1.27× keep-alive 256 KiB. On macOS, TWP vs YARP is ~0.91× keep-alive tiny, ~0.74× new-connection, ~1.28× keep-alive 256 KiB. New-connection is Darwin SslStream-bound for TWP and YARP (handshake p99 SLO 500 ms on macOS only — Win/Linux stay at 200 ms). Who leads each row is the medal in the table.

Unary gRPC (H2 TLS)

Unary Echo RPC/s @ c=64 over H2 TLS→H2 TLS for Titanium, YARP, nginx (grpc_pass), HAProxy, and Envoy (OS-possible peers only). Not folded into the architecture-sensitive tables. Median of 3 repeats @ 41f4adee — 36853254836.

OS Titanium YARP nginx HAProxy Envoy
Windows 🥇 52,009
(96 MiB / 22.7% CPU)
31,057
(126 MiB / 45.5% CPU)
0
(143 MiB / 24.9% CPU)
Not possible Not possible
Linux 🥇 41,339
(117 MiB / 30.4% CPU)
24,277
(158 MiB / 41.9% CPU)
0
(114 MiB / 24.8% CPU)
8,952
(83 MiB / 24.4% CPU)
14,753
(127 MiB / 20.7% CPU)
macOS 28,466
(152 MiB / 11.5% CPU)
🥇 29,087
(189 MiB / 21.3% CPU)
0
(85 MiB / 2.4% CPU)
18,511
(104 MiB / 19.6% CPU)
16,457
(120 MiB / 16.1% CPU)

Unary gRPC (H2 TLS → h2c)

Unary Echo RPC/s @ c=64 over H2 TLS → h2c (edge TLS, cleartext H2 origin). Peers: Titanium, YARP, HAProxy, Envoy. Mode: compare-grpc (*-grpc-h2c). Median of 3 repeats @ 41f4adee — 36853254836.

Not possible: nginx column omitted (no H2 upstream).

OS Titanium YARP HAProxy Envoy
Windows 🥇 61,643
(90 MiB / 23.5% CPU)
36,011
(116 MiB / 48.1% CPU)
Not possible Not possible
Linux 🥇 43,166
(121 MiB / 29.6% CPU)
24,252
(161 MiB / 43.4% CPU)
7,782
(83 MiB / 24.5% CPU)
12,044
(127 MiB / 22.2% CPU)
macOS 🥇 22,187
(158 MiB / 9.5% CPU)
6,717
(193 MiB / 16.0% CPU)
7,750
(100 MiB / 13.9% CPU)
16,573
(119 MiB / 19.7% CPU)

WebSocket (H1 TLS → H1 TLS)

WebSocket echo round-trips/sec over dual-TLS H1 (proxy_ssl style). Mode: compare-ws-h1tls (*-duplex-ws-h1tls).

OS Titanium YARP nginx HAProxy Envoy
Windows 🥇 22,332
(101 MiB / 47.4% CPU)
21,366
(93 MiB / 43.1% CPU)
9,361
(142 MiB / 24.6% CPU)
Not possible Not possible
Linux 25,321
(141 MiB / 43.7% CPU)
22,634
(134 MiB / 44.1% CPU)
26,985
(103 MiB / 37.3% CPU)
26,853
(83 MiB / 39.7% CPU)
🥇 28,050
(127 MiB / 37.9% CPU)
macOS 13,710
(168 MiB / 18.8% CPU)
6,452
(148 MiB / 14.1% CPU)
9,309
(86 MiB / 16.6% CPU)
🥇 14,432
(100 MiB / 20.5% CPU)
11,461
(119 MiB / 20.6% CPU)

WebSocket (H2 TLS 8441 → H1)

WebSocket echo over RFC 8441 extended CONNECT (H2 TLS client → H1 plain origin). Mode: compare-ws-h2 (*-duplex-ws-h2).

The probe client sets NoDelay and writes each HTTP/2 frame as one TLS record. The previous Linux row (~1.5k at ~9% CPU, tied with HAProxy) was that client with Nagle on and the frame split across two TLS records, which waited out the ~40 ms delayed ACK. Median of 3 repeats @ f2061c27. Source: Linux 37004979850, Windows 37004984433, macOS 37004988043. macOS Titanium samples at c=64 were 6.1k / 7.7k / 19.4k; the cell is that median, not a tight result.

Not possible: nginx column omitted (no RFC 8441 extended CONNECT).

OS Titanium YARP HAProxy Envoy
Windows 🥇 28,294
(154 MiB / 43.7% CPU)
0
(152 MiB / 19.4% CPU)
Not possible Not possible
Linux 🥇 42,050
(200 MiB / 49.6% CPU)
0
(198 MiB / 43.0% CPU)
38,112
(83 MiB / 40.3% CPU)
0
(124 MiB / 0.3% CPU)
macOS 7,705
(288 MiB / 15.9% CPU)
0
(186 MiB / 9.8% CPU)
🥇 9,015
(101 MiB / 15.4% CPU)
0
(116 MiB / 0.4% CPU)

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;
};

Maintainer notes

CI cadence, shards, paste scripts, and gate floors for people refreshing these tables. Consumers can skip this section.

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
Pre-wiki smoke compare-product-smoke (Linux 2 shards, repeats=1) ~30–60 min; required before full product
Release / wiki compare-product (3 comparison-group shards × Win/Linux/mac) ~2–2½h wall (Free account queues beyond 20 jobs)
Unary gRPC compare-grpc Win/Linux/mac; H2↔H2 + H2→h2c (arm_shard 1/2, 2/2)
WebSocket dual-TLS compare-ws-h1tls Win/Linux/mac; H1 TLS→H1 TLS echo
WebSocket RFC 8441 compare-ws-h2 Win/Linux/mac; H2 TLS→H1 plain (nginx N/A)
Heavier tables compare-bodies (2 shards) / post / lossy / arch (3 shards) / tls-cost dispatch independently

See PERF-GATES.md.

Arm shards (comparison groups)

--arm-shard i/n (workflow input arm_shard) partitions wiki rows (Client×Origin + heavier/arch workload suffix), not individual proxy arms. Paste unions shard CSVs (paste-compare-product-wiki.ps1 -RunIds …; heavier RUNS lists). Table “Source: Actions” may list several run URLs for one table — do not mix SHAs. Free GitHub accounts allow 20 concurrent jobs (~34 for a full suite); extras queue, they do not fail. Dispatch product (and cross-version) first when wall clock matters.

One wiki row = one GHA job’s Client×Origin cell set: TWP + YARP + nginx + HAProxy + Envoy (when the OS can run them) + TWP Lite + Full stay on the same VM. Shards split rows, not individual proxies — so TWP÷YARP and Lite÷Reverse remain same-job ratios. Do not compare absolute RPS across shards (different VMs).

Gate thresholds

  • Reverse product signal: TWP ÷ closest peer ≥ 0.50 (the peer among YARP, nginx, HAProxy, and Envoy whose sustain is nearest). Edition CLI/Plus ratios floor at 0.50.
  • MITM overhead: Lite÷Reverse ≥ 0.25 and Full÷Reverse ≥ 0.25 (median of 3 GHA runs @ c=64). Absolute RPS moves with runner heat; ratios are the claim.
  • Do not enable the HTTP/2 MITM multi-origin relay pool (MaxOriginHttp2ConnectionsPerAuthority > 1 under interception): Tip A/B showed Lite err%~29 and RSS blow-up, which fails the Lite/Full÷Reverse floors. Multi-origin remains gate-off compressed-relay only.
  • Editions: see PERF-GATES.md and validate-edition-gates.ps1.
  • Cross-version (Gate 2) before a major tag: run compare-cross-version (reverse matrix, routes unset) on develop vs committed 6.0 baselines (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

Paste / regenerate

Product refresh and heavier tables:

pwsh tools/RpsLoadProbe/run-rps.ps1 -Mode compare-product
pwsh tools/RpsLoadProbe/run-rps.ps1 -Mode compare-editions
pwsh tools/RpsLoadProbe/validate-edition-gates.ps1 -CsvPath tools/RpsLoadProbe/results/rps-ramp-*.csv
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

Harness details: RpsLoadProbe. 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 (intentional RPS tradeoff).

Clone this wiki locally