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 @ 0ff3673c — 32685356597. 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: Win / Linux RSS in Block B cells (was ~848 / ~626 MiB); 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 61,873
(53 MiB / 44.7% CPU)
61,983
(53 MiB / 44.7% CPU)
100%
origin-direct-bombardier bombardier 46,950
(54 MiB / 27.5% CPU)
47,181
(54 MiB / 27.5% CPU)
76.1%
bare-reverse-http1 dotnet-httpclient 30,801
(55 MiB / 45.9% CPU)
30,801
(55 MiB / 45.9% CPU)
49.7%
nginx-reverse-http1 dotnet-httpclient 19,151
(120 MiB / 24.8% CPU)
19,451
(120 MiB / 24.8% CPU)
31.4%
yarp-reverse-http1 dotnet-httpclient 26,913
(85 MiB / 49.8% CPU)
26,913
(85 MiB / 49.8% CPU)
43.4%
twp-reverse-http1 dotnet-httpclient 🥇 31,869
(77 MiB / 48.6% CPU)
31,869
(77 MiB / 48.6% CPU)
51.4%

Linux (ubuntu-latest)

Arm Generator Sustain Peak % of origin-HttpClient
origin-direct dotnet-httpclient 97,911
(79 MiB / 44.1% CPU)
97,912
(79 MiB / 44.1% CPU)
100%
origin-direct-bombardier bombardier 61,344
(79 MiB / 38.2% CPU)
61,541
(79 MiB / 38.2% CPU)
62.9%
bare-reverse-http1 dotnet-httpclient 46,344
(70 MiB / 46.7% CPU)
46,344
(70 MiB / 46.7% CPU)
47.3%
nginx-reverse-http1 dotnet-httpclient 🥇 55,817
(72 MiB / 41.2% CPU)
55,817
(72 MiB / 41.2% CPU)
57.0%
yarp-reverse-http1 dotnet-httpclient 41,821
(118 MiB / 49.6% CPU)
41,821
(118 MiB / 49.6% CPU)
42.7%
twp-reverse-http1 dotnet-httpclient 48,510
(89 MiB / 50.9% CPU)
48,510
(89 MiB / 50.9% CPU)
49.5%

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 15,025
(137 MiB / 23.7% CPU)
15,025
(137 MiB / 23.7% CPU)
0.43× 1.00×
yarp-reverse-http2 dotnet-httpclient 35,176
(94 MiB / 50.7% CPU)
35,176
(94 MiB / 50.7% CPU)
1.00× 2.34×
twp-reverse-http2-cleartext dotnet-httpclient 🥇 37,842
(149 MiB / 52.9% CPU)
37,842
(149 MiB / 52.9% CPU)
1.08× 2.52×

Linux (ubuntu-latest)

Arm Generator Sustain Peak ÷YARP ÷nginx
nginx-reverse-http2 dotnet-httpclient 21,348
(97 MiB / 23.4% CPU)
29,587
(97 MiB / 23.4% CPU)
0.66× 1.00×
yarp-reverse-http2 dotnet-httpclient 44,626
(122 MiB / 48.5% CPU)
44,626
(122 MiB / 48.5% CPU)
1.00× 1.51×
twp-reverse-http2-cleartext dotnet-httpclient 🥇 49,714
(227 MiB / 52.3% CPU)
49,714
(227 MiB / 52.3% CPU)
1.11× 1.68×

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 18,857
(164 MiB / 49.4% CPU)
18,857
(164 MiB / 49.4% CPU)
1.00× —
twp-reverse-http3-cleartext dotnet-httpclient 🥇 19,758
(233 MiB / 44.5% CPU)
19,758
(233 MiB / 44.5% CPU)
1.05× —

Linux (ubuntu-latest)

Arm Generator Sustain Peak ÷YARP ÷nginx
nginx-reverse-http3-cleartext dotnet-httpclient 0
(108 MiB / 22% CPU)
25,054
(108 MiB / 22% CPU)
0.90× 1.00×
yarp-reverse-http3-cleartext dotnet-httpclient 27,950
(193 MiB / 49.8% CPU)
27,950
(193 MiB / 49.8% CPU)
1.00× 1.12×
twp-reverse-http3-cleartext dotnet-httpclient 🥇 31,466
(304 MiB / 52.4% CPU)
31,466
(304 MiB / 52.4% CPU)
1.13× 1.26×

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/MITM/bridges @ 1f2d0eee — same 32688076110, 32688077908, bridges 32685354747 @ 0ff3673c (product tip; wiki paste @ 1f2d0eee). 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). 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 🥇 42,165
(115 MiB / 50.6% CPU)
42,165
(115 MiB / 50.6% CPU)
Not possible Not possible 41,148
(111 MiB / 50.9% CPU)
41,148
(111 MiB / 50.9% 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 🥇 56,585
(195 MiB / 52.6% CPU)
56,585
(195 MiB / 52.6% CPU)
Not possible Not possible 52,676
(87 MiB / 54% CPU)
52,676
(87 MiB / 54% CPU)
Reverse HTTP/2 · plain HTTP/2 · plain 🥇 122,084
(74 MiB / 35.2% CPU)
122,084
(74 MiB / 35.2% CPU)
Not possible Not possible 89,134
(100 MiB / 52.3% CPU)
89,134
(100 MiB / 52.3% CPU)
Reverse HTTP/2 · plain HTTP/2 · TLS 🥇 74,430
(81 MiB / 38.1% CPU)
74,430
(81 MiB / 38.1% CPU)
Not possible Not possible 55,671
(95 MiB / 47.9% CPU)
55,671
(95 MiB / 47.9% CPU)
Reverse HTTP/2 · plain HTTP/3 · QUIC 🥇 42,251
(185 MiB / 53.1% CPU)
42,251
(185 MiB / 53.1% CPU)
Not possible (no QUIC) Not possible (no QUIC) 41,584
(137 MiB / 53.5% CPU)
41,584
(137 MiB / 53.5% CPU)
Reverse HTTP/2 · TLS HTTP/1 · plain 🥇 54,493
(200 MiB / 52.6% CPU)
54,493
(200 MiB / 52.6% CPU)
11,057
(137 MiB / 24.4% CPU)
11,057
(137 MiB / 24.4% CPU)
47,759
(95 MiB / 52.4% CPU)
47,759
(95 MiB / 52.4% CPU)
Reverse HTTP/2 · TLS HTTP/2 · plain 🥇 112,459
(92 MiB / 35.7% CPU)
112,459
(92 MiB / 35.7% CPU)
Not possible Not possible 77,976
(104 MiB / 53.2% CPU)
77,976
(104 MiB / 53.2% CPU)
Reverse HTTP/2 · TLS HTTP/3 · QUIC 🥇 40,976
(201 MiB / 56.3% CPU)
40,976
(201 MiB / 56.3% CPU)
Not possible (no QUIC) Not possible (no QUIC) 37,267
(137 MiB / 53.6% CPU)
37,267
(137 MiB / 53.6% CPU)
Reverse HTTP/3 · QUIC HTTP/1 · plain 🥇 27,046
(255 MiB / 46.4% CPU)
27,046
(255 MiB / 46.4% CPU)
Not possible (no QUIC) Not possible (no QUIC) 24,085
(162 MiB / 51% CPU)
24,085
(162 MiB / 51% CPU)
Reverse HTTP/3 · QUIC HTTP/2 · TLS 🥇 35,922
(233 MiB / 46% CPU)
35,922
(233 MiB / 46% CPU)
Not possible (no QUIC) Not possible (no QUIC) 32,152
(181 MiB / 50.9% CPU)
32,152
(181 MiB / 50.9% CPU)
Reverse HTTP/3 · QUIC HTTP/3 · QUIC 🥇 14,958
(190 MiB / 49.7% CPU)
14,958
(190 MiB / 49.7% CPU)
Not possible (no QUIC) Not possible (no QUIC) 11,653
(162 MiB / 52.1% CPU)
11,653
(162 MiB / 52.1% 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.20× (25,525 / 21,341); H1 TLS terminate ≈ 1.13×. Bridges: all published TWP÷YARP ≥1.00× — H3→H1 ≈ 1.12× (27,046 / 24,085; closed prior 0.993× gap). Prefer ratios over absolute RPS on GHA VMs. MITM publishes the same 15 Client×Origin pairs as Reverse (inspectable/decrypt), then dual-crypto extras (CONNECT, TLS↔TLS). nginx/YARP cannot MITM.

Linux — Titanium vs nginx vs YARP

Median of 3 repeats on ubuntu-latest (4 vCPU / 16 GiB). Same/MITM @ 1f2d0eee (32688076110, 32688077908); bridges @ 0ff3673c (32685354747). 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.

TWP÷nginx H1 plain reverse ≈ 0.83 (46,284 / 55,862); TWP÷YARP H1 plain ≈ 1.12×.

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 🥇 48,038
(158 MiB / 48.1% CPU)
48,038
(158 MiB / 48.1% CPU)
Not possible Not possible 46,712
(145 MiB / 47.9% CPU)
46,712
(145 MiB / 47.9% CPU)
Reverse HTTP/1 · TLS HTTP/3 · QUIC 🥇 30,886
(163 MiB / 51.8% CPU)
30,886
(163 MiB / 51.8% CPU)
Not possible (no H3 origin) Not possible (no H3 origin) 29,371
(179 MiB / 50.7% CPU)
29,371
(179 MiB / 50.7% CPU)
Reverse HTTP/2 · plain HTTP/1 · plain 66,809
(229 MiB / 49.7% CPU)
66,809
(229 MiB / 49.7% CPU)
Not possible Not possible 🥇 67,822
(119 MiB / 48.4% CPU)
67,822
(119 MiB / 48.4% CPU)
Reverse HTTP/2 · plain HTTP/2 · plain 🥇 123,947
(104 MiB / 40.6% CPU)
123,947
(104 MiB / 40.6% CPU)
Not possible Not possible 90,109
(130 MiB / 47.4% CPU)
90,109
(130 MiB / 47.4% CPU)
Reverse HTTP/2 · plain HTTP/2 · TLS 🥇 74,148
(108 MiB / 39.6% CPU)
74,148
(108 MiB / 39.6% CPU)
Not possible Not possible 59,392
(138 MiB / 45.8% CPU)
59,392
(138 MiB / 45.8% CPU)
Reverse HTTP/2 · plain HTTP/3 · QUIC 🥇 38,554
(230 MiB / 50% CPU)
38,554
(230 MiB / 50% CPU)
Not possible (no H3 origin) Not possible (no H3 origin) 37,816
(164 MiB / 47.3% CPU)
37,816
(164 MiB / 47.3% CPU)
Reverse HTTP/2 · TLS HTTP/1 · plain 🥇 62,181
(232 MiB / 48.3% CPU)
62,181
(232 MiB / 48.3% CPU)
29,622
(96 MiB / 22.9% CPU)
29,622
(96 MiB / 22.9% CPU)
58,331
(121 MiB / 47.3% CPU)
58,331
(121 MiB / 47.3% CPU)
Reverse HTTP/2 · TLS HTTP/2 · plain 🥇 113,846
(114 MiB / 39.4% CPU)
113,846
(114 MiB / 39.4% CPU)
Not possible Not possible 71,600
(133 MiB / 46% CPU)
71,600
(133 MiB / 46% CPU)
Reverse HTTP/2 · TLS HTTP/3 · QUIC 🥇 35,985
(234 MiB / 49.7% CPU)
35,985
(234 MiB / 49.7% CPU)
Not possible (no H3 origin) Not possible (no H3 origin) 33,568
(176 MiB / 47.7% CPU)
33,568
(176 MiB / 47.7% CPU)
Reverse HTTP/3 · QUIC HTTP/1 · plain 🥇 34,129
(308 MiB / 46.6% CPU)
34,129
(308 MiB / 46.6% CPU)
0
(109 MiB / 19.1% CPU)
40,727
(109 MiB / 19.1% CPU)
31,205
(203 MiB / 48% CPU)
31,205
(203 MiB / 48% CPU)
Reverse HTTP/3 · QUIC HTTP/2 · TLS 🥇 33,159
(276 MiB / 47.1% CPU)
33,159
(276 MiB / 47.1% CPU)
Not possible (no H3→H2) Not possible (no H3→H2) 32,811
(210 MiB / 45.6% CPU)
32,811
(210 MiB / 45.6% CPU)
Reverse HTTP/3 · QUIC HTTP/3 · QUIC 🥇 26,711
(286 MiB / 49.3% CPU)
26,711
(286 MiB / 49.3% CPU)
Not possible (no H3 origin) Not possible (no H3 origin) 21,999
(210 MiB / 47.8% CPU)
21,999
(210 MiB / 47.8% 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 — within ~1.5% of YARP). 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 @ 21396a4d. Source: Actions 32608567396 (compare-post). Tip remasure @ 1f2d0eee blocked: three-process harness since bf01825b hangs TWP H1 keep-alive POST (RPS ≈ concurrency; YARP/nginx POST OK). H2/H3 POST tip medians were healthy (~3.8k / ~2.3k Win) but H1 POST needs a product fix before republishing.

Client Origin TWP sustain TWP peak nginx sustain nginx peak YARP sustain YARP peak
HTTP/1 · TLS HTTP/1 · plain 🥇 7,594 7,819 425
(137 MiB / 24.6% CPU)
433
(137 MiB / 24.6% CPU)
5,921
(118 MiB / 57.2% CPU)
5,927
(118 MiB / 57.2% CPU)
HTTP/2 · TLS HTTP/1 · plain 🥇 6,006
(189 MiB / 49.2% CPU)
6,006
(189 MiB / 49.2% CPU)
423
(138 MiB / 24.8% CPU)
444
(138 MiB / 24.8% CPU)
4,880
(119 MiB / 51.1% CPU)
4,880
(119 MiB / 51.1% CPU)
HTTP/3 · QUIC HTTP/1 · plain 🥇 2,772
(123 MiB / 41.9% CPU)
2,816
(123 MiB / 41.9% CPU)
Not possible Not possible 2,769
(201 MiB / 50.3% CPU)
2,861
(201 MiB / 50.3% CPU)

TWP leads H1 POST (1.28× YARP), H2 POST (1.23× YARP), and H3 POST (~1.00× YARP).

Linux — POST 64 KiB request + 64 KiB response

Median of 3 repeats @ 21396a4d. Source: Actions 32608567396 (compare-post).

Client Origin TWP sustain TWP peak nginx sustain nginx peak YARP sustain YARP peak
HTTP/1 · TLS HTTP/1 · plain 🥇 4,415 4,415 4,277
(96 MiB / 48.7% CPU)
4,292
(96 MiB / 48.7% CPU)
3,406
(170 MiB / 55.3% CPU)
3,406
(170 MiB / 55.3% CPU)
HTTP/2 · TLS HTTP/1 · plain 🥇 3,350
(212 MiB / 49.6% CPU)
3,350
(212 MiB / 49.6% CPU)
2,071
(99 MiB / 22.6% CPU)
2,147
(99 MiB / 22.6% CPU)
2,776
(170 MiB / 48.1% CPU)
2,776
(170 MiB / 48.1% CPU)
HTTP/3 · QUIC HTTP/1 · plain 🥇 2,919
(237 MiB / 44.3% CPU)
2,919
(237 MiB / 44.3% CPU)
799
(108 MiB / 24.7% CPU)
802
(108 MiB / 24.7% CPU)
2,771
(244 MiB / 50.2% CPU)
2,771
(244 MiB / 50.2% CPU)

Linux nginx H1/H2/H3 POST completed (nginx.org mainline). TWP÷YARP H3 POST ≈ 1.05×; TWP÷nginx H3 ≈ 3.65×.

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. Median of 3 repeats on windows-latest @ 1f2d0eee. Source: 32688085411 (compare-lossy). H3 on GHA Windows collapses through the userspace UDP shim (sustain ≈ 0); use the laptop lab for the Windows H3 signal.

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 Not measured (GHA UDP-shim) Not measured Not possible (no QUIC) Not possible Not measured (GHA UDP-shim) Not measured

TWP H2 HOL leads (~4.8× YARP). H1 ≈ parity with YARP.

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 ≈ parity with 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 @ 1f2d0eee (32688089789) (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 tip hang (RPS≈8) is the same three-process POST body issue as compare-post; H2/H3 early and slow/duplex/WS cells below are tip medians.

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
(98 MiB / 4.1% CPU)
248
(98 MiB / 4.1% CPU)
218
(240 MiB / 24.4% CPU)
218
(240 MiB / 24.4% CPU)
241
(110 MiB / 4.9% CPU)
241
(110 MiB / 4.9% CPU)
Slow consumer (256 KiB GET, throttled client read) HTTP/2 · TLS HTTP/1 · plain 248
(118 MiB / 5.7% CPU)
248
(118 MiB / 5.7% CPU)
198
(141 MiB / 24.4% CPU)
198
(141 MiB / 24.4% CPU)
🥇 256
(113 MiB / 7.3% CPU)
256
(113 MiB / 7.3% CPU)
Slow consumer (256 KiB GET, throttled client read) HTTP/3 · QUIC HTTP/1 · plain 🥇 263
(99 MiB / 18% CPU)
263
(99 MiB / 18% CPU)
Not possible (no QUIC) Not possible 257
(172 MiB / 21.2% CPU)
257
(172 MiB / 21.2% CPU)
Early response (origin writes after first request chunk) HTTP/1 · TLS HTTP/1 · plain blocked (POST hang) blocked 413
(137 MiB / 24.7% CPU)
413
(137 MiB / 24.7% CPU)
🥇 5,383
(135 MiB / 55.1% CPU)
5,383
(135 MiB / 55.1% CPU)
Early response (origin writes after first request chunk) HTTP/2 · TLS HTTP/1 · plain 🥇 5,786
(162 MiB / 48% CPU)
5,786
(162 MiB / 48% CPU)
401
(139 MiB / 24.6% CPU)
401
(139 MiB / 24.6% CPU)
3,452
(132 MiB / 53.5% CPU)
3,452
(132 MiB / 53.5% CPU)
Early response (origin writes after first request chunk) HTTP/3 · QUIC HTTP/1 · plain 🥇 2,683
(164 MiB / 43.3% CPU)
2,683
(164 MiB / 43.3% CPU)
Not possible (no QUIC) Not possible 2,261
(206 MiB / 52.9% CPU)
2,261
(206 MiB / 52.9% CPU)
Duplex (both directions live) HTTP/2 · TLS HTTP/2 · TLS 1,670
(132 MiB / 23.3% CPU)
1,670
(132 MiB / 23.3% CPU)
Not possible Not possible 🥇 2,666
(117 MiB / 41% CPU)
2,666
(117 MiB / 41% CPU)
Duplex (WebSocket / extended CONNECT) HTTP/1 · TLS HTTP/1 · plain 🥇 35,743
(96 MiB / 42.6% CPU)
35,743
(96 MiB / 42.6% CPU)
24,549
(137 MiB / 24.9% CPU)
24,549
(137 MiB / 24.9% CPU)
33,006
(86 MiB / 44.1% CPU)
33,006
(86 MiB / 44.1% 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 467
(119 MiB / 9.8% CPU)
467
(119 MiB / 9.8% CPU)
🥇 472
(99 MiB / 5.7% CPU)
472
(99 MiB / 5.7% CPU)
420
(144 MiB / 13.9% CPU)
420
(144 MiB / 13.9% CPU)
Slow consumer (256 KiB GET, throttled client read) HTTP/2 · TLS HTTP/1 · plain 476
(146 MiB / 19% CPU)
476
(146 MiB / 19% CPU)
🥇 478
(109 MiB / 12.4% CPU)
478
(109 MiB / 12.4% CPU)
470
(152 MiB / 23.8% CPU)
470
(152 MiB / 23.8% CPU)
Slow consumer (256 KiB GET, throttled client read) HTTP/3 · QUIC HTTP/1 · plain 🥇 474
(134 MiB / 35.6% CPU)
474
(134 MiB / 35.6% CPU)
427
(133 MiB / 24.2% CPU)
427
(133 MiB / 24.2% CPU)
471
(198 MiB / 43.8% CPU)
471
(198 MiB / 43.8% CPU)
Early response (origin writes after first request chunk) HTTP/1 · TLS HTTP/1 · plain blocked (POST hang) blocked 🥇 4,375
(94 MiB / 48.8% CPU)
4,375
(94 MiB / 48.8% CPU)
3,413
(177 MiB / 55.4% CPU)
3,413
(177 MiB / 55.4% CPU)
Early response (origin writes after first request chunk) HTTP/2 · TLS HTTP/1 · plain 🥇 3,448
(223 MiB / 48.2% CPU)
3,448
(223 MiB / 48.2% CPU)
2,147
(98 MiB / 22.4% CPU)
2,147
(98 MiB / 22.4% CPU)
2,528
(147 MiB / 48.7% CPU)
2,528
(147 MiB / 48.7% CPU)
Early response (origin writes after first request chunk) HTTP/3 · QUIC HTTP/1 · plain 🥇 3,135
(231 MiB / 45.5% CPU)
3,135
(231 MiB / 45.5% CPU)
789
(105 MiB / 24.1% CPU)
789
(105 MiB / 24.1% CPU)
2,234
(270 MiB / 47.3% CPU)
2,234
(270 MiB / 47.3% CPU)
Duplex (both directions live) HTTP/2 · TLS HTTP/2 · TLS 579
(141 MiB / 12.9% CPU)
579
(141 MiB / 12.9% CPU)
Not possible Not possible 🥇 1,862
(153 MiB / 51.7% CPU)
1,862
(153 MiB / 51.7% CPU)
Duplex (WebSocket / extended CONNECT) HTTP/1 · TLS HTTP/1 · plain 34,995
(122 MiB / 44.9% CPU)
34,995
(122 MiB / 44.9% CPU)
🥇 39,340
(96 MiB / 37.2% CPU)
39,340
(96 MiB / 37.2% CPU)
31,648
(122 MiB / 45.8% CPU)
31,648
(122 MiB / 45.8% CPU)

Slow consumer is sleep-bound; H1/H2/H3 sit in the same band once bodies stream. Early-response H2/H3: TWP leads. H1 early blocked by keep-alive POST hang under three-process. WebSocket: TWP leads Windows ≈ 1.08× YARP (35,743 / 33,006); 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