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

Performance

Titanium targets low-overhead MITM proxying: connection pooling, HTTP/2 multiplexing, and buffer reuse. Numbers below are Release measurements with RpsLoadProbe (and BenchmarkDotNet / Basic example where noted). Publishable tables cite GitHub Actions medians on matched 4 vCPU / 16 GiB runners. Absolute RPS still varies by OS kernel, TLS, and MsQuic packaging — compare within a table, not across Windows vs Linux.

Control arms: nginx (native C reverse-proxy ceiling; Linux is authoritative) and YARP (Yarp.ReverseProxy, managed .NET reverse proxy). Neither can MITM (no CONNECT / forged certs). FiddlerCore is not compared (commercial debugger license; not a throughput peer).

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

Both OS use the standard public-repo GitHub-hosted runner class (4 vCPU / 16 GiB / 14 GB SSD). Same harness knobs (workflow_dispatch RPS saturation: warmup 2s / measure 8s; concurrency 8, 16, 32, 64; median of 3 repeats). 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.

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)
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)
YARP Yarp.ReverseProxy 2.3.0
Harness RpsLoadProbe Release; median of 3 repeats where noted

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 @ 571b6fba — 32724323848. 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). H2→H1 after Http2PendingWork + lite wire + ClientSyntheticStreams / TryTakeStream: Win Block B TWP RSS 95 MiB vs YARP 92 MiB (1.04×); Linux 119 vs 120 MiB (0.99×). RPS still leads YARP.

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 51,355
(53 MiB / 41.7% CPU)
51,355
(53 MiB / 41.7% CPU)
100%
origin-direct-bombardier bombardier 39,006
(55 MiB / 21.8% CPU)
39,006
(55 MiB / 21.8% CPU)
76.0%
bare-reverse-http1 dotnet-httpclient 25,822
(55 MiB / 45.8% CPU)
25,822
(55 MiB / 45.8% CPU)
50.3%
nginx-reverse-http1 dotnet-httpclient 13,681
(120 MiB / 24.8% CPU)
13,681
(120 MiB / 24.8% CPU)
26.6%
yarp-reverse-http1 dotnet-httpclient 21,625
(88 MiB / 49.9% CPU)
21,625
(88 MiB / 49.9% CPU)
42.1%
twp-reverse-http1 dotnet-httpclient 🥇 25,734
(74 MiB / 48.6% CPU)
25,734
(74 MiB / 48.6% CPU)
50.1%

Linux (ubuntu-latest)

Arm Generator Sustain Peak % of origin-HttpClient
origin-direct dotnet-httpclient 77,349
(79 MiB / 43.4% CPU)
77,349
(79 MiB / 43.4% CPU)
100%
origin-direct-bombardier bombardier 48,453
(78 MiB / 37.6% CPU)
48,453
(78 MiB / 37.6% CPU)
62.6%
bare-reverse-http1 dotnet-httpclient 36,344
(67 MiB / 45.7% CPU)
36,344
(67 MiB / 45.7% CPU)
47.0%
nginx-reverse-http1 dotnet-httpclient 🥇 44,034
(71 MiB / 39.5% CPU)
44,034
(71 MiB / 39.5% CPU)
56.9%
yarp-reverse-http1 dotnet-httpclient 32,178
(114 MiB / 48.9% CPU)
32,178
(114 MiB / 48.9% CPU)
41.6%
twp-reverse-http1 dotnet-httpclient 36,789
(95 MiB / 49.8% CPU)
36,789
(95 MiB / 49.8% CPU)
47.6%

Reverse peers are about 28–57% of the origin-direct HttpClient peak on this runner class. 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 11,063
(137 MiB / 24.1% CPU)
11,229
(137 MiB / 24.1% CPU)
0.39× 1.00×
yarp-reverse-http2 dotnet-httpclient 29,146
(92 MiB / 54.8% CPU)
29,146
(92 MiB / 54.8% CPU)
1.00× 2.60×
twp-reverse-http2-cleartext dotnet-httpclient 🥇 35,153
(95 MiB / 53.5% CPU)
35,153
(95 MiB / 53.5% CPU)
1.21× 3.13×

Linux (ubuntu-latest)

Arm Generator Sustain Peak ÷YARP ÷nginx
nginx-reverse-http2 dotnet-httpclient 16,203
(95 MiB / 22.9% CPU)
22,490
(95 MiB / 22.9% CPU)
0.66× 1.00×
yarp-reverse-http2 dotnet-httpclient 34,003
(120 MiB / 48.6% CPU)
34,003
(120 MiB / 48.6% CPU)
1.00× 1.51×
twp-reverse-http2-cleartext dotnet-httpclient 🥇 38,998
(119 MiB / 51.9% CPU)
38,998
(119 MiB / 51.9% CPU)
1.15× 1.73×

Block C — H3→H1

Same layout as Block B. Requires QuicListener; nginx only with http_v3_module (Windows nginx has no QUIC).

Windows (windows-latest)

Arm Generator Sustain Peak ÷YARP ÷nginx
nginx-reverse-http3-cleartext dotnet-httpclient Not possible (no QUIC) Not possible — —
yarp-reverse-http3-cleartext dotnet-httpclient 14,558
(156 MiB / 48.9% CPU)
14,558
(156 MiB / 48.9% CPU)
1.00× —
twp-reverse-http3-cleartext dotnet-httpclient 🥇 15,274
(169 MiB / 44.9% CPU)
15,274
(169 MiB / 44.9% CPU)
1.05× —

Linux (ubuntu-latest)

Arm Generator Sustain Peak ÷YARP ÷nginx
nginx-reverse-http3-cleartext dotnet-httpclient 0
(104 MiB / 21.9% CPU)
19,156
(104 MiB / 21.9% CPU)
0.89× 1.00×
yarp-reverse-http3-cleartext dotnet-httpclient 21,501
(182 MiB / 49.1% CPU)
21,501
(182 MiB / 49.1% CPU)
1.00× 1.12×
twp-reverse-http3-cleartext dotnet-httpclient 🥇 24,011
(269 MiB / 51.9% CPU)
24,011
(269 MiB / 51.9% CPU)
1.12× 1.25×

How to read the tables

  • Mode: Reverse = transparent fixed-forward (may TLS-terminate to a cleartext origin, or re-encrypt to a configured HTTPS/QUIC origin). MITM = both legs are visible in the clear inside TWP — either by decrypting client TLS/QUIC (forged cert / CONNECT) or by accepting an already-cleartext client (explicit HTTP proxy / inspectable transparent reverse) while still speaking plain or TLS to the origin. nginx and YARP cannot do 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 only on that row or saturation block (never vs bare / origin-direct / bombardier). Product matrices and saturation Sustain: highest sustainable RPS. Saturation RPS cells embed (MiB / CPU%); 🥇 for Memory/CPU is omitted (footprint is informational). Omitted when only TWP can run the path (no fair multi-product comparison).
  • Not possible = product cannot do that path. Not measured = path exists but no published number yet for that OS.
pwsh tools/RpsLoadProbe/run-rps.ps1 -Mode compare-same
pwsh tools/RpsLoadProbe/run-rps.ps1 -Mode compare-terminate
pwsh tools/RpsLoadProbe/run-rps.ps1 -Mode compare-bridges
pwsh tools/RpsLoadProbe/run-rps.ps1 -Mode compare-mitm
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 YARP

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

Median of 3 repeats on windows-latest (4 vCPU / 16 GiB). Same @ 571b6fba — 32724329140; bridges 32724326362; saturation 32724323848. Three-process harness, parent-seeded loopback CA. 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> for TWP, nginx, and YARP (proxy child + descendant tree). H2→H1 / h2c→H1 / H2→H3 RSS after ClientSyntheticStreams. MITM matrix rows below are still the prior paste (no compare-mitm this pass). 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).

Mode Client Origin TWP sustain TWP peak nginx sustain nginx peak YARP sustain YARP peak
Reverse HTTP/1 · plain HTTP/1 · plain 🥇 25,525
(76 MiB / 52.9% CPU)
25,525
(76 MiB / 52.9% CPU)
13,325
(120 MiB / 24.8% CPU)
13,325
(120 MiB / 24.8% CPU)
21,341
(88 MiB / 49.6% CPU)
21,341
(88 MiB / 49.6% CPU)
Reverse HTTP/1 · plain HTTP/1 · TLS 🥇 21,069
(84 MiB / 52% CPU)
21,069
(84 MiB / 52% CPU)
Not possible Not possible 19,498
(100 MiB / 52.2% CPU)
19,498
(100 MiB / 52.2% CPU)
Reverse HTTP/1 · TLS HTTP/1 · plain 🥇 20,286
(82 MiB / 50.5% CPU)
20,286
(82 MiB / 50.5% CPU)
8,980
(137 MiB / 24.9% CPU)
8,980
(137 MiB / 24.9% CPU)
17,956
(103 MiB / 50.6% CPU)
17,956
(103 MiB / 50.6% CPU)
Reverse HTTP/1 · TLS HTTP/2 · TLS 🥇 24,800
(125 MiB / 44.9% CPU)
24,800
(125 MiB / 44.9% CPU)
Not possible Not possible 24,145
(107 MiB / 44.7% CPU)
24,145
(107 MiB / 44.7% CPU)
Reverse HTTP/1 · TLS HTTP/3 · QUIC 🥇 24,823
(118 MiB / 54% CPU)
24,823
(118 MiB / 54% CPU)
Not possible (no QUIC) Not possible (no QUIC) 24,684
(131 MiB / 52.9% CPU)
24,684
(131 MiB / 52.9% CPU)
Reverse HTTP/2 · plain HTTP/1 · plain 🥇 35,987
(84 MiB / 53.5% CPU)
35,987
(84 MiB / 53.5% CPU)
Not possible Not possible 31,519
(84 MiB / 54.5% CPU)
31,519
(84 MiB / 54.5% CPU)
Reverse HTTP/2 · plain HTTP/2 · plain 🥇 88,471
(72 MiB / 35.1% CPU)
88,471
(72 MiB / 35.1% CPU)
Not possible Not possible 64,996
(93 MiB / 52.1% CPU)
64,996
(93 MiB / 52.1% CPU)
Reverse HTTP/2 · plain HTTP/2 · TLS 🥇 73,711
(81 MiB / 35.1% CPU)
73,711
(81 MiB / 35.1% CPU)
Not possible Not possible 55,888
(97 MiB / 46.5% CPU)
55,888
(97 MiB / 46.5% CPU)
Reverse HTTP/2 · plain HTTP/3 · QUIC 🥇 30,496
(123 MiB / 51.9% CPU)
30,496
(123 MiB / 51.9% CPU)
Not possible (no QUIC) Not possible (no QUIC) 29,355
(121 MiB / 51.5% CPU)
29,355
(121 MiB / 51.5% CPU)
Reverse HTTP/2 · TLS HTTP/1 · plain 🥇 34,639
(99 MiB / 52.3% CPU)
34,639
(99 MiB / 52.3% CPU)
11,229
(137 MiB / 24.1% CPU)
11,229
(137 MiB / 24.1% CPU)
29,248
(94 MiB / 54.7% CPU)
29,248
(94 MiB / 54.7% CPU)
Reverse HTTP/2 · TLS HTTP/2 · plain 🥇 82,951
(88 MiB / 34.0% CPU)
82,951
(88 MiB / 34.0% CPU)
Not possible Not possible 55,715
(101 MiB / 50.5% CPU)
55,715
(101 MiB / 50.5% CPU)
Reverse HTTP/2 · TLS HTTP/3 · QUIC 🥇 29,644
(128 MiB / 56.4% CPU)
29,644
(128 MiB / 56.4% CPU)
Not possible (no QUIC) Not possible (no QUIC) 26,243
(119 MiB / 52.1% CPU)
26,243
(119 MiB / 52.1% CPU)
Reverse HTTP/3 · QUIC HTTP/1 · plain 15,068
(165 MiB / 45.3% CPU)
15,068
(165 MiB / 45.3% CPU)
Not possible (no QUIC) Not possible (no QUIC) 🥇 15,879
(151 MiB / 51.1% CPU)
15,879
(151 MiB / 51.1% CPU)
Reverse HTTP/3 · QUIC HTTP/2 · TLS 🥇 24,113
(199 MiB / 48.2% CPU)
24,113
(199 MiB / 48.2% CPU)
Not possible (no QUIC) Not possible (no QUIC) 21,218
(166 MiB / 47.7% CPU)
21,218
(166 MiB / 47.7% CPU)
Reverse HTTP/3 · QUIC HTTP/3 · QUIC 🥇 14,981
(173 MiB / 47.7% CPU)
14,981
(173 MiB / 47.7% CPU)
Not possible (no QUIC) Not possible (no QUIC) 11,620
(165 MiB / 51.7% CPU)
11,620
(165 MiB / 51.7% CPU)
MITM HTTP/1 · plain HTTP/1 · plain 🥇 33,785
(78 MiB / 51.9% CPU)
33,785
(78 MiB / 51.9% CPU)
Not possible (no MITM) Not possible (no MITM) Not possible (no MITM) Not possible (no MITM)
MITM HTTP/1 · plain HTTP/1 · TLS 🥇 30,763
(97 MiB / 51.2% CPU)
30,763
(97 MiB / 51.2% CPU)
Not possible (no MITM) Not possible (no MITM) Not possible (no MITM) Not possible (no MITM)
MITM HTTP/1 · TLS HTTP/1 · plain 🥇 30,785
(89 MiB / 49.6% CPU)
30,785
(89 MiB / 49.6% CPU)
Not possible (no MITM) Not possible (no MITM) Not possible (no MITM) Not possible (no MITM)
MITM HTTP/1 · TLS HTTP/2 · TLS 🥇 37,519
(114 MiB / 49.5% CPU)
37,519
(114 MiB / 49.5% CPU)
Not possible (no MITM) Not possible (no MITM) Not possible (no MITM) Not possible (no MITM)
MITM HTTP/1 · TLS HTTP/3 · QUIC 🥇 20,160
(114 MiB / 52% CPU)
20,160
(114 MiB / 52% CPU)
Not possible (no QUIC) Not possible (no QUIC) Not possible (no MITM) Not possible (no MITM)
MITM HTTP/2 · plain HTTP/1 · plain 🥇 45,273
(190 MiB / 50.7% CPU)
45,273
(190 MiB / 50.7% CPU)
Not possible (no MITM) Not possible (no MITM) Not possible (no MITM) Not possible (no MITM)
MITM HTTP/2 · plain HTTP/2 · plain 🥇 91,446
(73 MiB / 35.5% CPU)
91,446
(73 MiB / 35.5% CPU)
Not possible (no MITM) Not possible (no MITM) Not possible (no MITM) Not possible (no MITM)
MITM HTTP/2 · plain HTTP/2 · TLS 🥇 82,967
(87 MiB / 37.1% CPU)
82,967
(87 MiB / 37.1% CPU)
Not possible (no MITM) Not possible (no MITM) Not possible (no MITM) Not possible (no MITM)
MITM HTTP/2 · plain HTTP/3 · QUIC 🥇 33,186
(191 MiB / 54% CPU)
33,186
(191 MiB / 54% CPU)
Not possible (no QUIC) Not possible (no QUIC) Not possible (no MITM) Not possible (no MITM)
MITM HTTP/2 · TLS HTTP/1 · plain 🥇 43,911
(182 MiB / 54.6% CPU)
43,911
(182 MiB / 54.6% CPU)
Not possible (no MITM) Not possible (no MITM) Not possible (no MITM) Not possible (no MITM)
MITM HTTP/2 · TLS HTTP/2 · plain 🥇 86,099
(93 MiB / 39% CPU)
86,099
(93 MiB / 39% CPU)
Not possible (no MITM) Not possible (no MITM) Not possible (no MITM) Not possible (no MITM)
MITM HTTP/2 · TLS HTTP/3 · QUIC 🥇 32,660
(184 MiB / 53.4% CPU)
32,660
(184 MiB / 53.4% CPU)
Not possible (no QUIC) Not possible (no QUIC) Not possible (no MITM) Not possible (no MITM)
MITM HTTP/3 · QUIC HTTP/1 · plain 🥇 22,442
(241 MiB / 42.4% CPU)
22,442
(241 MiB / 42.4% CPU)
Not possible (no QUIC) Not possible (no QUIC) Not possible (no MITM) Not possible (no MITM)
MITM HTTP/3 · QUIC HTTP/2 · TLS 🥇 27,656
(218 MiB / 47.9% CPU)
27,656
(218 MiB / 47.9% CPU)
Not possible (no QUIC) Not possible (no QUIC) Not possible (no MITM) Not possible (no MITM)
MITM HTTP/3 · QUIC HTTP/3 · QUIC 🥇 16,808
(215 MiB / 47.2% CPU)
16,808
(215 MiB / 47.2% CPU)
Not possible (no QUIC) Not possible (no QUIC) Not possible (no MITM) Not possible (no MITM)
MITM HTTP/1 · plain (CONNECT) HTTP/1 · TLS 🥇 27,351
(105 MiB / 50.6% CPU)
27,351
(105 MiB / 50.6% CPU)
Not possible (no MITM) Not possible (no MITM) Not possible (no MITM) Not possible (no MITM)
MITM HTTP/1 · TLS HTTP/1 · TLS 🥇 28,338
(83 MiB / 48.4% CPU)
28,338
(83 MiB / 48.4% CPU)
Not possible (no MITM) Not possible (no MITM) Not possible (no MITM) Not possible (no MITM)
MITM HTTP/2 · TLS HTTP/2 · TLS 🥇 76,456
(89 MiB / 37.1% CPU)
76,456
(89 MiB / 37.1% CPU)
Not possible (no MITM) Not possible (no MITM) Not possible (no MITM) Not possible (no MITM)
MITM HTTP/2 · TLS HTTP/1 · TLS 🥇 39,157
(167 MiB / 53.3% CPU)
39,157
(167 MiB / 53.3% CPU)
Not possible (no MITM) Not possible (no MITM) Not possible (no MITM) Not possible (no MITM)
MITM HTTP/3 · QUIC HTTP/1 · TLS 🥇 20,318
(243 MiB / 41.8% CPU)
20,318
(243 MiB / 41.8% CPU)
Not possible (no QUIC) Not possible (no QUIC) Not possible (no MITM) Not possible (no MITM)

TWP÷YARP H1 plain ≈ 1.19× (25,439 / 21,337 from compare-same); H2 TLS→H1 ≈ 1.18× (34,639 / 29,248); h2c→H1 ≈ 1.14×. Bridges Memory after ClientSyntheticStreams: H2→H1 ≈ 1.05× (99 / 94 MiB), h2c→H1 ≈ 1.00× (84 / 84). H3→H1 Memory ≈ 1.09× (165 / 151); RPS on that bridges pass YARP-led (0.95×). Prefer ratios over absolute RPS on GHA VMs. MITM rows are prior paste (no compare-mitm this pass). nginx/YARP cannot MITM.

Linux — Titanium vs nginx vs YARP

Median of 3 repeats on ubuntu-latest (4 vCPU / 16 GiB). Same @ 571b6fba (32724329140); bridges 32724326362; saturation 32724323848. Warmup 2s / measure 8s; concurrency 8, 16, 32, 64. Linux nginx is the authoritative nginx baseline. The RPS workflow installs nginx.org mainline (http_v3_module) and libmsquic (QuicListener.IsSupported=true on ubuntu-latest). Prefer ratios over absolute RPS. RPS cells include peer (MiB / CPU%) as on Windows. H2→H1 RSS after ClientSyntheticStreams. MITM rows prior paste (no compare-mitm this pass).

TWP÷nginx H1 plain reverse ≈ 0.81 (31,210 / 38,365 from compare-same); TWP÷YARP H1 plain ≈ 1.12×. H2 TLS→H1 Memory ≈ 0.97× YARP (123 / 127 MiB bridges); RPS ≈ 1.17×.

Mode Client Origin TWP sustain TWP peak nginx sustain nginx peak YARP sustain YARP peak
Reverse HTTP/1 · plain HTTP/1 · plain 46,284
(82 MiB / 51.1% CPU)
46,284
(82 MiB / 51.1% CPU)
🥇 55,862
(72 MiB / 41.2% CPU)
55,862
(72 MiB / 41.2% CPU)
41,203
(114 MiB / 49.4% CPU)
41,203
(114 MiB / 49.4% CPU)
Reverse HTTP/1 · plain HTTP/1 · TLS 🥇 37,194
(103 MiB / 48.6% CPU)
37,194
(103 MiB / 48.6% CPU)
Not possible Not possible 33,500
(140 MiB / 49.2% CPU)
33,500
(140 MiB / 49.2% CPU)
Reverse HTTP/1 · TLS HTTP/1 · plain 36,518
(107 MiB / 49.1% CPU)
36,518
(107 MiB / 49.1% CPU)
🥇 43,011
(99 MiB / 41.6% CPU)
43,011
(99 MiB / 41.6% CPU)
32,495
(135 MiB / 50% CPU)
32,495
(135 MiB / 50% CPU)
Reverse HTTP/1 · TLS HTTP/2 · TLS 🥇 23,230
(148 MiB / 48.6% CPU)
23,230
(148 MiB / 48.6% CPU)
Not possible Not possible 22,278
(146 MiB / 48.1% CPU)
22,278
(146 MiB / 48.1% CPU)
Reverse HTTP/1 · TLS HTTP/3 · QUIC 🥇 17,619
(134 MiB / 52.4% CPU)
17,619
(134 MiB / 52.4% CPU)
Not possible (no H3 origin) Not possible (no H3 origin) 16,430
(156 MiB / 49.5% CPU)
16,430
(156 MiB / 49.5% CPU)
Reverse HTTP/2 · plain HTTP/1 · plain 🥇 37,404
(110 MiB / 53.0% CPU)
37,404
(110 MiB / 53.0% CPU)
Not possible Not possible 33,931
(113 MiB / 50.1% CPU)
33,931
(113 MiB / 50.1% CPU)
Reverse HTTP/2 · plain HTTP/2 · plain 🥇 64,632
(98 MiB / 40.2% CPU)
64,632
(98 MiB / 40.2% CPU)
Not possible Not possible 49,259
(121 MiB / 47.1% CPU)
49,259
(121 MiB / 47.1% CPU)
Reverse HTTP/2 · plain HTTP/2 · TLS 🥇 51,070
(110 MiB / 39.8% CPU)
51,070
(110 MiB / 39.8% CPU)
Not possible Not possible 38,998
(129 MiB / 45.9% CPU)
38,998
(129 MiB / 45.9% CPU)
Reverse HTTP/2 · plain HTTP/3 · QUIC 🥇 27,125
(142 MiB / 49.7% CPU)
27,125
(142 MiB / 49.7% CPU)
Not possible (no H3 origin) Not possible (no H3 origin) 26,267
(149 MiB / 46.9% CPU)
26,267
(149 MiB / 46.9% CPU)
Reverse HTTP/2 · TLS HTTP/1 · plain 🥇 34,529
(117 MiB / 52.4% CPU)
34,529
(117 MiB / 52.4% CPU)
16,203
(95 MiB / 22.9% CPU)
22,490
(95 MiB / 22.9% CPU)
29,386
(121 MiB / 49.9% CPU)
29,386
(121 MiB / 49.9% CPU)
Reverse HTTP/2 · TLS HTTP/2 · plain 🥇 59,122
(108 MiB / 39.7% CPU)
59,122
(108 MiB / 39.7% CPU)
Not possible Not possible 38,995
(128 MiB / 46.7% CPU)
38,995
(128 MiB / 46.7% CPU)
Reverse HTTP/2 · TLS HTTP/3 · QUIC 🥇 25,491
(144 MiB / 49.8% CPU)
25,491
(144 MiB / 49.8% CPU)
Not possible (no H3 origin) Not possible (no H3 origin) 22,556
(156 MiB / 46.8% CPU)
22,556
(156 MiB / 46.8% CPU)
Reverse HTTP/3 · QUIC HTTP/1 · plain 🥇 20,923
(270 MiB / 50.2% CPU)
20,923
(270 MiB / 50.2% CPU)
0
(102 MiB / 22.5% CPU)
14,882
(102 MiB / 22.5% CPU)
18,141
(181 MiB / 50.3% CPU)
18,141
(181 MiB / 50.3% CPU)
Reverse HTTP/3 · QUIC HTTP/2 · TLS 🥇 24,067
(245 MiB / 51.9% CPU)
24,067
(245 MiB / 51.9% CPU)
Not possible (no H3→H2) Not possible (no H3→H2) 21,122
(191 MiB / 47.6% CPU)
21,122
(191 MiB / 47.6% CPU)
Reverse HTTP/3 · QUIC HTTP/3 · QUIC 🥇 19,341
(214 MiB / 47.4% CPU)
19,341
(214 MiB / 47.4% CPU)
Not possible (no H3 origin) Not possible (no H3 origin) 15,760
(198 MiB / 47.5% CPU)
15,760
(198 MiB / 47.5% CPU)
MITM HTTP/1 · plain HTTP/1 · plain 🥇 56,421
(85 MiB / 51.3% CPU)
56,421
(85 MiB / 51.3% CPU)
Not possible (no MITM) Not possible (no MITM) Not possible (no MITM) Not possible (no MITM)
MITM HTTP/1 · plain HTTP/1 · TLS 🥇 46,215
(106 MiB / 47.1% CPU)
46,215
(106 MiB / 47.1% CPU)
Not possible (no MITM) Not possible (no MITM) Not possible (no MITM) Not possible (no MITM)
MITM HTTP/1 · TLS HTTP/1 · plain 🥇 46,892
(107 MiB / 48.4% CPU)
46,892
(107 MiB / 48.4% CPU)
Not possible (no MITM) Not possible (no MITM) Not possible (no MITM) Not possible (no MITM)
MITM HTTP/1 · TLS HTTP/2 · TLS 🥇 43,132
(151 MiB / 49.3% CPU)
43,132
(151 MiB / 49.3% CPU)
Not possible (no MITM) Not possible (no MITM) Not possible (no MITM) Not possible (no MITM)
MITM HTTP/1 · TLS HTTP/3 · QUIC 🥇 28,615
(153 MiB / 52% CPU)
28,615
(153 MiB / 52% CPU)
Not possible (no MITM) Not possible (no MITM) Not possible (no MITM) Not possible (no MITM)
MITM HTTP/2 · plain HTTP/1 · plain 🥇 59,946
(231 MiB / 50.5% CPU)
59,946
(231 MiB / 50.5% CPU)
Not possible (no MITM) Not possible (no MITM) Not possible (no MITM) Not possible (no MITM)
MITM HTTP/2 · plain HTTP/2 · plain 🥇 105,099
(107 MiB / 39.5% CPU)
105,099
(107 MiB / 39.5% CPU)
Not possible (no MITM) Not possible (no MITM) Not possible (no MITM) Not possible (no MITM)
MITM HTTP/2 · plain HTTP/2 · TLS 🥇 81,941
(111 MiB / 38% CPU)
81,941
(111 MiB / 38% CPU)
Not possible (no MITM) Not possible (no MITM) Not possible (no MITM) Not possible (no MITM)
MITM HTTP/2 · plain HTTP/3 · QUIC 🥇 34,410
(228 MiB / 49% CPU)
34,410
(228 MiB / 49% CPU)
Not possible (no MITM) Not possible (no MITM) Not possible (no MITM) Not possible (no MITM)
MITM HTTP/2 · TLS HTTP/1 · plain 🥇 55,620
(228 MiB / 49.5% CPU)
55,620
(228 MiB / 49.5% CPU)
Not possible (no MITM) Not possible (no MITM) Not possible (no MITM) Not possible (no MITM)
MITM HTTP/2 · TLS HTTP/2 · plain 🥇 96,923
(114 MiB / 38% CPU)
96,923
(114 MiB / 38% CPU)
Not possible (no MITM) Not possible (no MITM) Not possible (no MITM) Not possible (no MITM)
MITM HTTP/2 · TLS HTTP/3 · QUIC 🥇 32,204
(233 MiB / 49.3% CPU)
32,204
(233 MiB / 49.3% CPU)
Not possible (no MITM) Not possible (no MITM) Not possible (no MITM) Not possible (no MITM)
MITM HTTP/3 · QUIC HTTP/1 · plain 🥇 31,313
(319 MiB / 48.1% CPU)
31,313
(319 MiB / 48.1% CPU)
Not possible (no MITM) Not possible (no MITM) Not possible (no MITM) Not possible (no MITM)
MITM HTTP/3 · QUIC HTTP/2 · TLS 🥇 30,142
(283 MiB / 48.4% CPU)
30,142
(283 MiB / 48.4% CPU)
Not possible (no MITM) Not possible (no MITM) Not possible (no MITM) Not possible (no MITM)
MITM HTTP/3 · QUIC HTTP/3 · QUIC 🥇 23,327
(286 MiB / 45.9% CPU)
23,327
(286 MiB / 45.9% CPU)
Not possible (no MITM) Not possible (no MITM) Not possible (no MITM) Not possible (no MITM)
MITM HTTP/1 · plain (CONNECT) HTTP/1 · TLS 🥇 37,282
(131 MiB / 47.8% CPU)
37,282
(131 MiB / 47.8% CPU)
Not possible (no MITM) Not possible (no MITM) Not possible (no MITM) Not possible (no MITM)
MITM HTTP/1 · TLS HTTP/1 · TLS 🥇 39,263
(109 MiB / 46.1% CPU)
39,263
(109 MiB / 46.1% CPU)
Not possible (no MITM) Not possible (no MITM) Not possible (no MITM) Not possible (no MITM)
MITM HTTP/2 · TLS HTTP/2 · TLS 🥇 76,804
(119 MiB / 36.8% CPU)
76,804
(119 MiB / 36.8% CPU)
Not possible (no MITM) Not possible (no MITM) Not possible (no MITM) Not possible (no MITM)
MITM HTTP/2 · TLS HTTP/1 · TLS 🥇 43,868
(220 MiB / 46.8% CPU)
43,868
(220 MiB / 46.8% CPU)
Not possible (no MITM) Not possible (no MITM) Not possible (no MITM) Not possible (no MITM)
MITM HTTP/3 · QUIC HTTP/1 · TLS 🥇 26,330
(294 MiB / 45.7% CPU)
26,330
(294 MiB / 45.7% CPU)
Not possible (no MITM) Not possible (no MITM) Not possible (no MITM) Not possible (no MITM)

On this GHA shape, TWP H1 plain ÷ nginx H1 plain ≈ 0.83 (46,284 / 55,862). H1 TLS terminate ≈ 0.85 (36,518 / 43,011). TWP÷YARP H1 plain ≈ 1.12× (46,284 / 41,203). Bridges @ 0ff3673c: H3→H1 ≈ 1.09× (34,129 / 31,205), H3→H2 ≈ 1.01×, H2 TLS→H1 ≈ 1.07×, H1→H3 ≈ 1.05×; h2c→H1 ≈ 0.985× (66,809 / 67,822). Absolute RPS swings by VM; prefer the ratio and median across repeats. MITM publishes the same 15 Client×Origin pairs as Reverse (inspectable/decrypt), then dual-crypto extras (CONNECT, TLS↔TLS). nginx/YARP cannot MITM.

nginx HTTP/3: inbound QUIC terminate → cleartext H1 (nginx-reverse-http3-cleartext) @ 0ff3673c bridges: sustain 0 (p99/error SLO miss) / peak 40,727. TWP/YARP H3→H1 on this row are from the same bridges pass. nginx still cannot speak HTTP/3 to an origin (no H3 upstream in this conf).

YARP HTTP/3 (this matrix): TWP leads H3→H1 ≈ 1.09× (34,129 / 31,205), H3→H2 ≈ 1.01× (33,159 / 32,811). H1→H2 ≈ 1.03× (48,038 / 46,712). H1→H3 ≈ 1.05× (30,886 / 29,371). h2c→H3 ≈ 1.02×.

Windows vs Linux: both CI envs are 4 vCPU / 16 GiB, but do not compare absolute RPS across OS. Linux nginx leads H1 plain/TLS terminate (TWP second, ahead of YARP). Windows bridges @ 0ff3673c closed the last YARP-led tiny-GET cell (H3→H1 ≈ 1.12×). Cool laptop notes remain on Performance Local Lab.

Tiny JSON reverse is nginx’s best case on Linux

The tables above use ~64 B keep-alive GET on loopback. On Linux H1 reverse, nginx leads; YARP sits near TWP. Heavier bodies, POSTs, TLS handshake cost, and lossy/HOL workloads (below) change the picture. MITM rows remain TWP-only. nginx HTTP/3 is inbound-terminate only (see note above).

Why isn’t HTTP/3 > HTTP/2 > HTTP/1 in raw RPS?

For tiny JSON responses (~64 B) on loopback, that ordering is not expected: topology (TLS hop count, terminate vs MITM) dominates; HTTP/2 and HTTP/3 help multiplexing, not single-origin tiny-GET RPS. See the lossy tables below for a workload where protocol design matters.

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. 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.

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)

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

Body Client Origin TWP sustain TWP peak nginx sustain nginx peak YARP sustain YARP peak
64 KiB HTTP/1 · TLS HTTP/1 · plain 🥇 8,600
(98 MiB / 50.1% CPU)
8,600
(98 MiB / 50.1% CPU)
906
(137 MiB / 24.9% CPU)
906
(137 MiB / 24.9% CPU)
6,903
(113 MiB / 45.6% CPU)
6,903
(113 MiB / 45.6% CPU)
64 KiB HTTP/2 · TLS HTTP/1 · plain 🥇 8,011
(186 MiB / 45.6% CPU)
8,011
(186 MiB / 45.6% CPU)
779
(138 MiB / 25% CPU)
779
(138 MiB / 25% CPU)
5,789
(136 MiB / 49.5% CPU)
5,789
(136 MiB / 49.5% CPU)
64 KiB HTTP/3 · QUIC HTTP/1 · plain 🥇 4,127
(128 MiB / 40.9% CPU)
4,127
(128 MiB / 40.9% CPU)
Not possible (no QUIC) Not possible (no QUIC) 3,709
(189 MiB / 50.8% CPU)
3,709
(189 MiB / 50.8% CPU)
256 KiB HTTP/1 · TLS HTTP/1 · plain 🥇 2,824
(102 MiB / 48.7% CPU)
2,824
(102 MiB / 48.7% CPU)
249
(137 MiB / 24.9% CPU)
249
(137 MiB / 24.9% CPU)
1,884
(140 MiB / 48.5% CPU)
1,884
(140 MiB / 48.5% CPU)
256 KiB HTTP/2 · TLS HTTP/1 · plain 🥇 2,504
(154 MiB / 40.4% CPU)
2,504
(154 MiB / 40.4% CPU)
170
(139 MiB / 24.9% CPU)
170
(139 MiB / 24.9% CPU)
1,958
(101 MiB / 42.7% CPU)
1,958
(101 MiB / 42.7% CPU)
256 KiB HTTP/3 · QUIC HTTP/1 · plain 🥇 1,108
(129 MiB / 41.5% CPU)
1,108
(129 MiB / 41.5% CPU)
Not possible (no QUIC) Not possible (no QUIC) 1,048
(176 MiB / 45.3% CPU)
1,048
(176 MiB / 45.3% CPU)

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

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

Median of 3 repeats @ 1f2d0eee. Source: Actions 32688081527 (compare-bodies). Warmup 2s / measure 8s.

Body Client Origin TWP sustain TWP peak nginx sustain nginx peak YARP sustain YARP peak
64 KiB HTTP/1 · TLS HTTP/1 · plain 12,030
(185 MiB / 42.3% CPU)
12,030
(185 MiB / 42.3% CPU)
🥇 13,402
(98 MiB / 37.3% CPU)
13,402
(98 MiB / 37.3% CPU)
9,932
(169 MiB / 47.9% CPU)
9,932
(169 MiB / 47.9% CPU)
64 KiB HTTP/2 · TLS HTTP/1 · plain 🥇 8,850
(247 MiB / 39.5% CPU)
8,850
(247 MiB / 39.5% CPU)
6,886
(105 MiB / 24.3% CPU)
6,886
(105 MiB / 24.3% CPU)
7,294
(164 MiB / 47.7% CPU)
7,294
(164 MiB / 47.7% CPU)
64 KiB HTTP/3 · QUIC HTTP/1 · plain 🥇 7,504
(230 MiB / 42.9% CPU)
7,504
(230 MiB / 42.9% CPU)
2,417
(116 MiB / 17.5% CPU)
2,417
(116 MiB / 17.5% CPU)
5,191
(242 MiB / 52.6% CPU)
5,191
(242 MiB / 52.6% CPU)
256 KiB HTTP/1 · TLS HTTP/1 · plain 🥇 3,868
(124 MiB / 31.6% CPU)
3,868
(124 MiB / 31.6% CPU)
3,850
(99 MiB / 29% CPU)
3,850
(99 MiB / 29% CPU)
2,912
(175 MiB / 43.5% CPU)
2,912
(175 MiB / 43.5% CPU)
256 KiB HTTP/2 · TLS HTTP/1 · plain 🥇 2,249
(207 MiB / 31.8% CPU)
2,249
(207 MiB / 31.8% CPU)
1,804
(111 MiB / 23.3% CPU)
1,804
(111 MiB / 23.3% CPU)
1,919
(167 MiB / 44.9% CPU)
1,919
(167 MiB / 44.9% CPU)
256 KiB HTTP/3 · QUIC HTTP/1 · plain 🥇 1,864
(177 MiB / 41.9% CPU)
1,864
(177 MiB / 41.9% CPU)
642
(102 MiB / 18.2% CPU)
642
(102 MiB / 18.2% CPU)
1,533
(191 MiB / 49.8% CPU)
1,533
(191 MiB / 49.8% CPU)

On this GHA pass TWP÷YARP H1 TLS ≈ 1.21× (64 KiB) / 1.33× (256 KiB); H2→H1 ≈ 1.21× / 1.17×; H3→H1 ≈ 1.45× / 1.22×. TWP÷nginx H1 TLS ≈ 0.90 / 1.00. Absolute RPS swings by VM; prefer ratios.

Windows — POST 64 KiB request + 64 KiB response

Median of 3 repeats on windows-latest @ d3566486. Source: Actions 32711461804 (compare-post). H1 keep-alive POST hang fixed (session-lite + reusable guard).

Client Origin TWP sustain TWP peak nginx sustain nginx peak YARP sustain YARP peak
HTTP/1 · TLS HTTP/1 · plain 🥇 6,720
(92 MiB / 44% CPU)
6,720
(92 MiB / 44% CPU)
353
(137 MiB / 24.6% CPU)
353
(137 MiB / 24.6% CPU)
4,767
(113 MiB / 56.2% CPU)
4,767
(113 MiB / 56.2% CPU)
HTTP/2 · TLS HTTP/1 · plain 🥇 4,576
(183 MiB / 48.3% CPU)
4,576
(183 MiB / 48.3% CPU)
337
(137 MiB / 24.6% CPU)
337
(137 MiB / 24.6% CPU)
4,018
(117 MiB / 52.4% CPU)
4,018
(117 MiB / 52.4% CPU)
HTTP/3 · QUIC HTTP/1 · plain 🥇 2,346
(158 MiB / 41.3% CPU)
2,346
(158 MiB / 41.3% CPU)
Not possible Not possible 2,133
(156 MiB / 47.4% CPU)
2,133
(156 MiB / 47.4% CPU)

TWP leads H1 POST (1.41× YARP), H2 POST (1.14× YARP), and H3 POST (~1.10× YARP).

Linux — POST 64 KiB request + 64 KiB response

Median of 3 repeats @ d3566486. Source: Actions 32711461804 (compare-post).

Client Origin TWP sustain TWP peak nginx sustain nginx peak YARP sustain YARP peak
HTTP/1 · TLS HTTP/1 · plain 🥇 4,674
(125 MiB / 44.9% CPU)
4,674
(125 MiB / 44.9% CPU)
4,029
(96 MiB / 48.9% CPU)
4,029
(96 MiB / 48.9% CPU)
3,104
(178 MiB / 55.2% CPU)
3,104
(178 MiB / 55.2% CPU)
HTTP/2 · TLS HTTP/1 · plain 🥇 3,102
(230 MiB / 49.1% CPU)
3,102
(230 MiB / 49.1% CPU)
1,963
(99 MiB / 22.3% CPU)
1,963
(99 MiB / 22.3% CPU)
2,489
(148 MiB / 47.9% CPU)
2,489
(148 MiB / 47.9% CPU)
HTTP/3 · QUIC HTTP/1 · plain 🥇 2,903
(227 MiB / 43.5% CPU)
2,903
(227 MiB / 43.5% CPU)
750
(106 MiB / 24.1% CPU)
750
(106 MiB / 24.1% CPU)
2,555
(253 MiB / 50% CPU)
2,555
(253 MiB / 50% CPU)

Linux nginx H1/H2/H3 POST completed (nginx.org mainline). TWP÷YARP H1 ≈ 1.51×; H2 ≈ 1.25×; H3 ≈ 1.14×. TWP÷nginx H3 ≈ 3.87×.

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

Userspace 5 ms one-way delay + 1% TCP connection stall (H1/H2) or UDP datagram drop (H3); 64 KiB GET. H1/H2: median of 3 repeats on windows-latest @ 1f2d0eee — 32688085411 (compare-lossy). H3: GHA Windows userspace UDP shim collapses (sustain 0); published H3 row is the laptop quic-http3 remasure under the same delay/loss workload (Performance Local Lab, windows-20260822-lossy-h3-quic/).

Client Origin TWP sustain TWP peak nginx sustain nginx peak YARP sustain YARP peak
HTTP/1 · TLS HTTP/1 · plain 663
(107 MiB / 5.4% CPU)
663
(107 MiB / 5.4% CPU)
645
(137 MiB / 18.3% CPU)
645
(137 MiB / 18.3% CPU)
🥇 663
(122 MiB / 5.1% CPU)
663
(122 MiB / 5.1% CPU)
HTTP/2 · TLS HTTP/1 · plain 🥇 86
(126 MiB / 1.9% CPU)
86
(126 MiB / 1.9% CPU)
18
(138 MiB / 1.1% CPU)
18
(138 MiB / 1.1% CPU)
18
(98 MiB / 1.7% CPU)
18
(98 MiB / 1.7% CPU)
HTTP/3 · QUIC HTTP/1 · plain 🥇 1,572 1,572 Not possible (no QUIC) Not possible 0 50

TWP H2 HOL leads (4.8× YARP). H3 is the protocol-shape win vs H2 HOL on the same lossy session (112× H2); YARP H3 did not hold the p99 SLO under this userspace UDP shim (sustain 0, peak 50).

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

Median of 3 repeats @ 1f2d0eee. Source: 32688085411 (compare-lossy; lossy H3 uses quic-http3).

Client Origin TWP sustain TWP peak nginx sustain nginx peak YARP sustain YARP peak
HTTP/1 · TLS HTTP/1 · plain 🥇 1,204
(140 MiB / 13.1% CPU)
1,204
(140 MiB / 13.1% CPU)
1,209
(96 MiB / 6.8% CPU)
1,209
(96 MiB / 6.8% CPU)
1,199
(152 MiB / 16.3% CPU)
1,199
(152 MiB / 16.3% CPU)
HTTP/2 · TLS HTTP/1 · plain 🥇 314
(192 MiB / 6.6% CPU)
314
(192 MiB / 6.6% CPU)
40
(99 MiB / 0.4% CPU)
40
(99 MiB / 0.4% CPU)
44
(119 MiB / 1.6% CPU)
44
(119 MiB / 1.6% CPU)
HTTP/3 · QUIC HTTP/1 · plain 328
(147 MiB / 12.2% CPU)
328
(147 MiB / 12.2% CPU)
86
(107 MiB / 2.2% CPU)
86
(107 MiB / 2.2% CPU)
🥇 332
(185 MiB / 20.7% CPU)
332
(185 MiB / 20.7% CPU)

TWP H2 HOL ≫ YARP (~7.1×). H3 TWP÷YARP ≈ 0.99×.

Architecture-sensitive

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 @ d3566486 (32711464527) (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. H1 early-response unblocked by session-lite keep-alive POST fix.

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

Windows

Scenario Client Origin TWP sustain TWP peak nginx sustain nginx peak YARP sustain YARP peak
Slow consumer (256 KiB GET, throttled client read) HTTP/1 · TLS HTTP/1 · plain 🥇 248
(93 MiB / 4.4% CPU)
248
(93 MiB / 4.4% CPU)
194
(141 MiB / 24.5% CPU)
194
(141 MiB / 24.5% CPU)
244
(107 MiB / 5% CPU)
244
(107 MiB / 5% CPU)
Slow consumer (256 KiB GET, throttled client read) HTTP/2 · TLS HTTP/1 · plain 248
(118 MiB / 5% CPU)
248
(118 MiB / 5% CPU)
169
(141 MiB / 24.8% CPU)
169
(141 MiB / 24.8% CPU)
🥇 256
(109 MiB / 6.9% CPU)
256
(109 MiB / 6.9% CPU)
Slow consumer (256 KiB GET, throttled client read) HTTP/3 · QUIC HTTP/1 · plain 🥇 268
(98 MiB / 19.1% CPU)
268
(98 MiB / 19.1% CPU)
Not possible (no QUIC) Not possible 259
(161 MiB / 22.4% CPU)
259
(161 MiB / 22.4% CPU)
Early response (origin writes after first request chunk) HTTP/1 · TLS HTTP/1 · plain 🥇 5,034
(85 MiB / 38% CPU)
5,034
(85 MiB / 38% CPU)
349
(136 MiB / 24.6% CPU)
349
(136 MiB / 24.6% CPU)
3,441
(114 MiB / 41.6% CPU)
3,441
(114 MiB / 41.6% CPU)
Early response (origin writes after first request chunk) HTTP/2 · TLS HTTP/1 · plain 🥇 4,285
(143 MiB / 43.3% CPU)
4,285
(143 MiB / 43.3% CPU)
300
(137 MiB / 24.5% CPU)
300
(137 MiB / 24.5% CPU)
3,016
(109 MiB / 39.9% CPU)
3,016
(109 MiB / 39.9% CPU)
Early response (origin writes after first request chunk) HTTP/3 · QUIC HTTP/1 · plain 🥇 2,263
(136 MiB / 43.5% CPU)
2,263
(136 MiB / 43.5% CPU)
Not possible (no QUIC) Not possible 1,985
(155 MiB / 53.6% CPU)
1,985
(155 MiB / 53.6% CPU)
Duplex (both directions live) HTTP/2 · TLS HTTP/2 · TLS 885
(113 MiB / 13.1% CPU)
885
(113 MiB / 13.1% CPU)
Not possible Not possible 🥇 2,293
(119 MiB / 38.5% CPU)
2,293
(119 MiB / 38.5% CPU)
Duplex (WebSocket / extended CONNECT) HTTP/1 · TLS HTTP/1 · plain 🥇 31,149
(93 MiB / 44.8% CPU)
31,149
(93 MiB / 44.8% CPU)
18,388
(137 MiB / 24.7% CPU)
18,388
(137 MiB / 24.7% CPU)
28,676
(89 MiB / 44.6% CPU)
28,676
(89 MiB / 44.6% CPU)

Linux

Scenario Client Origin TWP sustain TWP peak nginx sustain nginx peak YARP sustain YARP peak
Slow consumer (256 KiB GET, throttled client read) HTTP/1 · TLS HTTP/1 · plain 464
(116 MiB / 9.7% CPU)
464
(116 MiB / 9.7% CPU)
🥇 473
(98 MiB / 6.6% CPU)
473
(98 MiB / 6.6% CPU)
419
(147 MiB / 15.3% CPU)
419
(147 MiB / 15.3% CPU)
Slow consumer (256 KiB GET, throttled client read) HTTP/2 · TLS HTTP/1 · plain 468
(140 MiB / 19.1% CPU)
468
(140 MiB / 19.1% CPU)
🥇 477
(108 MiB / 13.7% CPU)
477
(108 MiB / 13.7% CPU)
472
(144 MiB / 25% CPU)
472
(144 MiB / 25% CPU)
Slow consumer (256 KiB GET, throttled client read) HTTP/3 · QUIC HTTP/1 · plain 🥇 472
(133 MiB / 36.3% CPU)
472
(133 MiB / 36.3% CPU)
410
(122 MiB / 24.4% CPU)
410
(122 MiB / 24.4% CPU)
469
(197 MiB / 41% CPU)
469
(197 MiB / 41% CPU)
Early response (origin writes after first request chunk) HTTP/1 · TLS HTTP/1 · plain 🥇 4,637
(132 MiB / 47.7% CPU)
4,637
(132 MiB / 47.7% CPU)
3,998
(95 MiB / 50.6% CPU)
3,998
(95 MiB / 50.6% CPU)
3,088
(173 MiB / 56.8% CPU)
3,088
(173 MiB / 56.8% CPU)
Early response (origin writes after first request chunk) HTTP/2 · TLS HTTP/1 · plain 🥇 3,132
(230 MiB / 46.4% CPU)
3,132
(230 MiB / 46.4% CPU)
1,925
(99 MiB / 22.4% CPU)
1,925
(99 MiB / 22.4% CPU)
2,273
(132 MiB / 48.3% CPU)
2,273
(132 MiB / 48.3% CPU)
Early response (origin writes after first request chunk) HTTP/3 · QUIC HTTP/1 · plain 🥇 2,875
(220 MiB / 45.9% CPU)
2,875
(220 MiB / 45.9% CPU)
708
(107 MiB / 24% CPU)
708
(107 MiB / 24% CPU)
2,042
(248 MiB / 48.7% CPU)
2,042
(248 MiB / 48.7% CPU)
Duplex (both directions live) HTTP/2 · TLS HTTP/2 · TLS 378
(136 MiB / 9.2% CPU)
378
(136 MiB / 9.2% CPU)
Not possible Not possible 🥇 1,706
(149 MiB / 45.4% CPU)
1,706
(149 MiB / 45.4% CPU)
Duplex (WebSocket / extended CONNECT) HTTP/1 · TLS HTTP/1 · plain 31,574
(122 MiB / 43.7% CPU)
31,574
(122 MiB / 43.7% CPU)
🥇 32,771
(96 MiB / 35.6% CPU)
32,771
(96 MiB / 35.6% CPU)
26,797
(122 MiB / 44.5% CPU)
26,797
(122 MiB / 44.5% CPU)

Slow consumer is sleep-bound; H1/H2/H3 sit in the same band. Early-response H1/H2/H3: TWP leads (H1 early ≈ 1.46× / 1.50× YARP Win/Linux after keep-alive POST fix). Duplex H2: YARP leads by design — Win ≈ 0.39× (885 / 2,293), Linux ≈ 0.22× (378 / 1,706); irreducible concurrent-copier cell (see IO model). WebSocket: TWP leads Windows ≈ 1.09× YARP; 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

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

Workload TWP sustain TWP peak nginx sustain nginx peak YARP sustain YARP peak
Keep-alive · tiny GET 🥇 25,426
(81 MiB / 50.7% CPU)
25,426
(81 MiB / 50.7% CPU)
12,864
(137 MiB / 24.8% CPU)
12,864
(137 MiB / 24.8% CPU)
23,364
(107 MiB / 50.4% CPU)
23,364
(107 MiB / 50.4% CPU)
New-connection · tiny GET 🥇 769
(86 MiB / 10% CPU)
769
(86 MiB / 10% CPU)
256
(136 MiB / 24.6% CPU)
256
(136 MiB / 24.6% CPU)
753
(114 MiB / 9.9% CPU)
753
(114 MiB / 9.9% CPU)
Keep-alive · 256 KiB GET 🥇 2,886
(116 MiB / 44.5% CPU)
2,886
(116 MiB / 44.5% CPU)
259
(136 MiB / 25% CPU)
259
(136 MiB / 25% CPU)
2,512
(137 MiB / 45.6% CPU)
2,512
(137 MiB / 45.6% CPU)

Linux

Median of 3 repeats @ 1f2d0eee. Source: Actions 32688087733 (compare-tls-cost).

Workload TWP sustain TWP peak nginx sustain nginx peak YARP sustain YARP peak
Keep-alive · tiny GET 51,331
(96 MiB / 49% CPU)
51,331
(96 MiB / 49% CPU)
🥇 66,322
(99 MiB / 39.8% CPU)
66,322
(99 MiB / 39.8% CPU)
45,016
(136 MiB / 49.7% CPU)
45,016
(136 MiB / 49.7% CPU)
New-connection · tiny GET 1,666
(125 MiB / 39% CPU)
1,666
(125 MiB / 39% CPU)
🥇 1,729
(101 MiB / 34.6% CPU)
1,729
(101 MiB / 34.6% CPU)
1,652
(152 MiB / 38.4% CPU)
1,652
(152 MiB / 38.4% CPU)
Keep-alive · 256 KiB GET 5,208
(122 MiB / 29.8% CPU)
5,208
(122 MiB / 29.8% CPU)
🥇 5,272
(100 MiB / 27% CPU)
5,272
(100 MiB / 27% CPU)
4,130
(175 MiB / 42.4% CPU)
4,130
(175 MiB / 42.4% 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) ~186 µs, ~17.5 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*'
Benchmark Setup Mean Allocated / op
HTTP/1 GET through proxy Passthrough 186 µs 17.5 KB
HTTP/2 multiplexed GETs 10 concurrent streams 3.0 ms / batch ~14 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