YARP vs. NGINX vs. ARC2 Proxy: Technical Differences and Performance
A technical comparison of three reverse proxies — Microsoft's YARP on .NET, the C-based NGINX, and the Rust-and-Tokio ARC2 Proxy — covering their architectures, memory-safety and garbage-collection trade-offs, and how those choices show up in throughput and tail latency under load.
Introduction
Reverse proxies all promise the same headline job — sit in front of your servers, terminate TLS, route requests, cache responses — but under the hood they make very different engineering trade-offs. In this article we compare three of them: YARP (Microsoft's Yet Another Reverse Proxy, built on .NET), NGINX (the C-based workhorse that runs a large share of the web), and ARC2 Proxy (our own reverse proxy written in Rust on top of the Tokio async runtime). The goal is not to crown a single winner for every situation — it is to explain where each one's design naturally shines and what that means for latency, throughput, and operational cost.
The three architectures at a glance
| Property | YARP | NGINX | ARC2 Proxy |
|---|---|---|---|
| Language / runtime | C# on .NET (managed, JIT + GC) | C (native, no runtime) | Rust on Tokio (native, no GC) |
| Concurrency model | async/await over the .NET thread pool |
Event loop, one worker process per core | async/await over a work-stealing Tokio scheduler |
| Configuration | Code + JSON / ASP.NET config | Declarative config files, reload on change | TOML config + live rules from a control API |
| Extensibility | First-class: it is a library you build on | Modules (compiled) or Lua/njs scripting | Native Rust middleware compiled in |
| Memory safety | Managed, safe | Manual, unsafe by default | Safe by default (Rust's ownership model) |
| Typical footprint | Higher (runtime + GC heap) | Very low | Very low (no runtime, no GC) |
YARP: a reverse proxy as a library
YARP is unusual in this line-up because it is not a standalone product you deploy — it is a toolkit for building your own proxy inside an ASP.NET Core application. That is its greatest strength and its main cost. If your organisation already lives in the .NET ecosystem, YARP lets you express routing and transformation logic in the same language, with the same tooling, dependency injection, and observability as the rest of your services. You can intercept requests with ordinary C# middleware and reuse existing libraries.
The trade-offs follow directly from running on a managed runtime:
- Garbage collection. The .NET GC is excellent, but a proxy is a latency-sensitive, high-allocation workload, and under heavy load GC pauses introduce jitter in the latency tail (p99) even when the average looks fine.
- Runtime overhead. Each instance carries the .NET runtime and a managed heap, so the baseline memory footprint is higher than a native proxy — which matters when you run many instances.
- Warm-up. JIT compilation means the first requests after a deploy are slower until hot paths are compiled, though ahead-of-time compilation narrows this gap.
YARP is a strong choice when the proxy is really an application — complex, code-driven routing tightly coupled to a .NET backend — and less compelling when you simply need a fast, lean edge in front of many origins.
NGINX: the proven native workhorse
NGINX earned its ubiquity honestly. Written in C with a carefully engineered event-driven architecture, it uses a small number of worker processes (typically one per CPU core), each running a non-blocking event loop that can juggle thousands of concurrent connections with minimal per-connection memory. For serving static files and caching, it is extremely fast and battle-tested across two decades of production traffic.
Its trade-offs are the flip side of that maturity:
- Written in C. The performance is superb, but C gives you no memory-safety guarantees; historically a meaningful share of serious NGINX and module vulnerabilities have been memory-safety bugs.
- Configuration is declarative but rigid. The config language is powerful for common cases, but complex conditional logic quickly becomes awkward, and truly dynamic behaviour usually means reaching for Lua (OpenResty) or the njs scripting module.
- Dynamic reconfiguration. The open-source edition reloads configuration by spawning new workers; live, API-driven changes to upstreams and rules are more limited without the commercial edition or third-party modules.
For the classic job — terminate TLS, cache aggressively, reverse-proxy to a handful of upstreams — NGINX remains an excellent default and a fair performance yardstick, which is exactly why we benchmark ARC2 Proxy against it.
ARC2 Proxy: native speed with memory safety
ARC2 Proxy is written in Rust and built on Tokio, the same async runtime that powers a large part of the modern Rust network stack. The design goal is to combine the raw, no-runtime performance profile of a native proxy like NGINX with the memory safety and developer ergonomics of a modern language — and to make dynamic, per-domain configuration a first-class feature rather than an afterthought.
The architecture leans on a few specific choices:
- No garbage collector. Rust manages memory through ownership at compile time, so there are no GC pauses. Latency stays predictable under load, which shows up most clearly in the p99 tail where GC-based proxies wobble.
- Tokio's work-stealing scheduler. Asynchronous tasks are distributed across worker threads that steal work from each other, keeping all cores busy without the developer hand-partitioning connections.
- In-memory caching on the hot path. Cached responses are served from memory with lookups designed to be O(1), using concurrent hash maps (DashMap) so many requests can read the cache simultaneously without lock contention.
- rustls for TLS. TLS is terminated with rustls, a modern, memory-safe TLS implementation, with SNI-based certificate resolution so a single instance can serve many domains securely.
- Live rules. Per-domain routing, caching, WAF, rate-limiting and waiting-room settings are delivered dynamically, so changes take effect without a config-file reload cycle.
- Memory safety by default. Rust's ownership and borrow checking eliminate whole classes of vulnerabilities — use-after-free, buffer overflows, data races — at compile time rather than in production.
Performance: what the benchmarks show
In our own like-for-like benchmarks serving a cached "Hello World!" response under identical hardware and configuration, ARC2 Proxy and NGINX are very close at low and moderate concurrency — as you would expect from two native, event-driven designs — and ARC2 Proxy pulls ahead at high concurrency, where its latency stays lower and more consistent as the request volume climbs. The gap is most visible in the extreme load tests, which is precisely where a garbage-collected proxy would be expected to fall furthest behind, and where consistent tail latency matters most to real users.
The general performance ordering that follows from the architectures is: native, no-GC proxies (NGINX and ARC2 Proxy) form the top tier for raw throughput and tail-latency consistency, while a managed-runtime proxy like YARP trades some of that consistency for the productivity of building your edge in application code. Which trade-off is right depends entirely on what you are optimising for.
How to choose
- Choose YARP if your proxy logic is really application logic, you are all-in on .NET, and developer velocity inside that ecosystem matters more than squeezing out the last few percent of tail latency.
- Choose NGINX if you want a mature, ubiquitous, declaratively configured proxy for fairly static routing and caching, and you are comfortable with its configuration model and the realities of a C codebase.
- Choose ARC2 Proxy if you want native-tier performance with predictable tail latency, memory safety by default, and dynamic per-domain configuration — caching, routing, WAF, rate limiting, waiting rooms and health-aware routing — managed from a single control plane.
Conclusion
There is no universally "fastest" reverse proxy — there are different points on a design curve. YARP optimises for integration with .NET, NGINX for proven, lean, declarative operation, and ARC2 Proxy for combining native performance with memory safety and live configurability. For latency-sensitive, high-traffic edges where consistent tail latency and safe, dynamic reconfiguration matter, ARC2 Proxy's Rust-and-Tokio foundation is built precisely for that job.