YARP vs Nginx: A Quick Performance Comparison for .NET
A simple, friendly look at YARP vs Nginx as a reverse proxy: how each one works, real benchmark numbers, tuning tips, and how to pick the right one.
One gate for the whole market
Picture a busy vegetable market with many small shops inside. Now picture a single main gate at the front. Every customer enters through that one gate. A friendly guard stands there. When a customer asks for mangoes, the guard points them to the mango shop. When someone asks for onions, the guard sends them to the onion shop. The shops never deal with the crowd at the gate. They just serve whoever the guard sends in.
That guard at the gate is a reverse proxy. Your shops are your app servers. The customers are users sending requests over the internet. The reverse proxy takes every request first, decides which app server should handle it, and carries the answer back.
Two popular guards for this job are YARP and Nginx. YARP stands for "Yet Another Reverse Proxy." It is built by Microsoft and runs on .NET, the same world as your ASP.NET Core apps. Nginx is an older, very fast web server written in C that has run a huge part of the internet for years.
In this guide we will compare them in a calm, simple way. We will see how each one works, look at real benchmark numbers, learn a few tuning tricks, and finish with a clear way to pick. By the end you will know which guard fits your market.
What a reverse proxy actually does
Before we compare, let us make sure the picture is clear. Without a proxy, users would need to know the address of each app server. That is messy and unsafe. With a proxy, there is one front door.
A reverse proxy gives you several jobs in one place:
- Load balancing — share the work across many servers so none gets crushed.
- TLS termination — handle HTTPS once at the front, so backends can stay simple.
- Routing — send
/apito one group of servers and/imagesto another. - Health checks — stop sending traffic to a server that is sick.
- Security — one place to add rate limits, headers, and filtering.
Both YARP and Nginx do all of this. The difference is how they do it and how they feel to work with.
Meet YARP
YARP is a library, not a separate program. You add it to an ASP.NET Core app. It runs on Kestrel, the same fast web server inside every modern .NET app. Because it lives inside .NET, you configure it the same way you configure any ASP.NET Core service, and you can extend it with plain C#.
Here is the smallest YARP you can build. First, the C# in Program.cs:
var builder = WebApplication.CreateBuilder(args);
// Register YARP and load routes/clusters from appsettings.json
builder.Services
.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
var app = builder.Build();
// Plug the proxy into the request pipeline
app.MapReverseProxy();
app.Run();That is the whole program. AddReverseProxy() registers the YARP services. LoadFromConfig(...) reads the routes and clusters from your configuration. MapReverseProxy() wires the proxy into the pipeline so it can start forwarding traffic.
Next, the configuration in appsettings.json. A route says "match this incoming path." A cluster says "here are the real servers to forward to."
// appsettings.json (shown here as text)
// {
// "ReverseProxy": {
// "Routes": {
// "apiRoute": {
// "ClusterId": "apiCluster",
// "Match": { "Path": "/api/{**catch-all}" }
// }
// },
// "Clusters": {
// "apiCluster": {
// "LoadBalancingPolicy": "RoundRobin",
// "Destinations": {
// "d1": { "Address": "https://localhost:5001/" },
// "d2": { "Address": "https://localhost:5002/" }
// }
// }
// }
// }
// }Notice the path pattern /api/{**catch-all}. The double-star part means "match the rest of the path too." Because the config lives in IConfiguration, YARP can even reload it without restarting when the file changes.
The real power shows when you need custom logic. You can write a transform in C# to change headers or paths before the request goes out:
builder.Services
.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"))
.AddTransforms(context =>
{
// Add a header that tells the backend who proxied the request
context.AddRequestTransform(transform =>
{
transform.ProxyRequest.Headers.Add("X-Proxied-By", "YARP");
return ValueTask.CompletedTask;
});
});This is the heart of YARP's appeal. If you know C#, you already know how to bend YARP to your will. No new mini-language, no separate process.
How a request flows through YARP
Steps
Request
User hits Kestrel
Route match
Path matched to a cluster
Transform
Headers or path tweaked in C#
Forward
Sent to a healthy destination
Response
Reply streamed back to user
Meet Nginx
Nginx is a separate program written in C. You install it on a server, write a config file, and start it. It has powered a large slice of the web for many years and is known for being lean and very fast, especially at serving static files and handling huge numbers of connections.
A basic Nginx reverse proxy config looks like this:
// nginx.conf (shown here as text)
// upstream api_cluster {
// server 127.0.0.1:5001;
// server 127.0.0.1:5002;
// keepalive 64; # reuse connections to the backend
// }
//
// server {
// listen 80;
//
// location /api/ {
// proxy_pass http://api_cluster;
// proxy_http_version 1.1; # needed for keepalive
// proxy_set_header Connection ""; # needed for keepalive
// }
// }An upstream block is Nginx's version of a YARP cluster. A location block is its version of a route. The big detail here is keepalive 64 together with proxy_http_version 1.1. This tells Nginx to reuse connections to the backend instead of opening a brand new TCP connection for every single request. We will see why that matters so much in a moment.
Nginx is configured through its own text format. It is powerful, but it is a separate language from your app. To add custom logic you often reach for Lua modules or extra tools, which is more to learn for a pure .NET team.
The benchmark: the part everyone wants
Now the fun part. People love to ask "which one is faster?" The honest answer is "it depends, and the defaults lie to you."
When you first run a benchmark with both tools at their default settings, YARP often looks like it crushes Nginx. In one widely shared test at 200 concurrent users, YARP handled around 36,000 requests per second while Nginx sat near 10,000. That is roughly 3.6x more. YARP's latency also looked better.
But this is not the whole story. Nginx ships with very conservative defaults on purpose, so it is safe on tiny servers. The biggest default that hurts a reverse proxy is missing upstream keepalive. Without it, Nginx opens a fresh connection to the backend for every request, which is slow and wasteful.
Once you tune Nginx — add keepalive, raise worker_connections, enable HTTP/1.1 to the backend — the numbers flip. A well-tuned Nginx usually leads YARP by about 10 to 20 percent in raw throughput, with slightly lower latency too. For example, at 200 users a tuned Nginx might give a p90 latency near 6.3 ms while YARP gives around 7.8 ms.
Here is a simple side-by-side of the kind of numbers people report. Treat these as a feel for the shape, not exact gospel — your hardware and config will differ.
| Scenario | Tool | Requests/sec (approx) | p90 latency (approx) |
|---|---|---|---|
| Default settings | YARP | 36,000 | 7.8 ms |
| Default settings | Nginx | 10,000 | 21 ms |
| After tuning | YARP | 36,000 | 7.8 ms |
| After tuning | Nginx | 40,000+ | 6.3 ms |
The story in one line: out of the box YARP wins, but tuned Nginx edges ahead.
Why the defaults matter so much
It is worth pausing on the keepalive point, because it explains the whole twist. A TCP connection is like a phone call. Setting up a new call takes time: dialing, ringing, the handshake. If you hang up after every single sentence and redial for the next one, you waste a lot of time on dialing.
Without keepalive, default Nginx hangs up after every request and redials for the next. With keepalive, it keeps the line open and reuses it. That one change can roughly double the requests per second for a busy proxy.
YARP, by contrast, has sensible connection pooling on by default, because it grew up in a modern .NET world where this is expected. So YARP looks great with zero tuning. This is not magic; it is just a better starting point. Nginx can reach the same place, but you have to ask for it.
A short checklist of the Nginx knobs that matter most for a reverse proxy:
| Setting | What it does | Why it matters |
|---|---|---|
keepalive | Reuse upstream connections | Biggest single win; can double throughput |
proxy_http_version 1.1 | Use HTTP/1.1 to backend | Required for keepalive to work |
worker_connections | Connections per worker | Raises how much traffic one worker handles |
worker_processes | Number of workers | Usually set to the number of CPU cores |
gzip | Compress responses | Less data on the wire for text |
Beyond raw speed
Raw requests per second is fun, but it is rarely the thing that decides a project. A few other factors usually weigh more.
Developer experience. With YARP, the proxy is just another .NET app. Same appsettings.json, same logging, same dependency injection, same debugger. You can set a breakpoint inside a transform and step through it. For a .NET team, this is a big comfort.
Static files and edge features. Nginx is superb at serving static files, caching, and acting as a hardened edge in front of everything. If you already serve lots of images, CSS, and downloads, Nginx earns its place.
Ecosystem fit. Nginx is everywhere, runs on tiny machines, and has decades of guides. YARP shines inside Kubernetes and cloud setups where you want one language and one toolchain across proxy and apps. With .NET 10 now the LTS release, YARP is a first-class, fully supported part of the ASP.NET Core fundamentals docs.
Choosing between YARP and Nginx
Steps
Start
You need a reverse proxy
Team is .NET?
If yes, YARP feels natural
Need custom C# logic?
Transforms favor YARP
Heavy static files?
Static + edge favors Nginx
Pick
Match the tool to the need
A small load-balancing example in YARP
To make the cluster idea concrete, here is a tiny health-check and load-balancing setup, written in code instead of config. This shows how naturally these features fit into a .NET app:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddReverseProxy()
.LoadFromMemory(
routes: new[]
{
new Yarp.ReverseProxy.Configuration.RouteConfig
{
RouteId = "api",
ClusterId = "apiCluster",
Match = new() { Path = "/api/{**rest}" }
}
},
clusters: new[]
{
new Yarp.ReverseProxy.Configuration.ClusterConfig
{
ClusterId = "apiCluster",
LoadBalancingPolicy = "PowerOfTwoChoices",
Destinations = new Dictionary<string, Yarp.ReverseProxy.Configuration.DestinationConfig>
{
["d1"] = new() { Address = "https://localhost:5001/" },
["d2"] = new() { Address = "https://localhost:5002/" }
}
}
});
var app = builder.Build();
app.MapReverseProxy();
app.Run();The LoadBalancingPolicy of PowerOfTwoChoices is a smart default. It picks two random backends and sends the request to the one with fewer active requests. This avoids the worst backend while staying cheap to compute. YARP also supports RoundRobin, LeastRequests, and others. Note the route path /api/{**rest} is written inside config and code, not in prose, so the curly braces are safe.
So which one should you pick?
Here is the honest summary, free of hype.
- If your team is all .NET and you value a smooth, debuggable, one-language setup, pick YARP. It is fast enough for almost everyone, especially with its good defaults, and it feels like home.
- If you serve lots of static content, want a hardened edge proxy with decades of mileage, or already run Nginx across your fleet, pick Nginx. Just remember to tune it, especially upstream keepalive.
The benchmark gap, once both are tuned, is small — around 10 to 20 percent. For most real apps, that gap is not what makes or breaks you. Database queries, network hops, and your own code usually matter far more. So weigh developer experience and fit at least as much as raw numbers.
Quick recap
- A reverse proxy is the single front gate that takes every request and sends it to the right backend server.
- YARP is a .NET library that runs on Kestrel. You configure it like any ASP.NET Core app and extend it with C#.
- Nginx is a fast C program with its own config language, famous for serving static files and handling huge connection counts.
- With default settings, YARP often looks far faster (around 3.6x in one test) because Nginx defaults are conservative.
- The main culprit is upstream keepalive. Once you enable it and tune Nginx, Nginx usually leads by about 10 to 20 percent.
- Pick YARP for .NET-native comfort and custom C# logic. Pick Nginx for static files and a battle-tested edge.
- For most apps the performance gap is small. Developer experience and ecosystem fit usually matter more than the last few requests per second.
References and further reading
- Get started with YARP — Microsoft Learn
- YARP Configuration Files — Microsoft Learn
- YARP project home — dotnet.github.io/yarp
- YARP vs Nginx: A Quick Performance Comparison — Milan Jovanović
- Tuning NGINX for Performance — F5
- Performance Tuning: Tips & Tricks — NGINX Community Blog
Related Posts
Rate Limiting in ASP.NET Core: A Simple, Complete Guide
Learn rate limiting in ASP.NET Core with simple examples. Understand fixed window, sliding window, token bucket, and concurrency limiters, with diagrams, code, and real-world advice on which to pick.
Caching in ASP.NET Core: Make Your App Fast (The Easy Way)
Learn caching in ASP.NET Core with simple examples. Understand in-memory cache, distributed Redis cache, HybridCache, and output cache, with diagrams, code, and clear advice on which to use and when.
.NET Aspire: A Game Changer for Cloud-Native Development
A beginner-friendly guide to .NET Aspire, the cloud-native stack that orchestrates your services, databases, and dashboards with one simple command.
Implementing API Gateway Authentication With YARP in .NET
Learn to build a secure API gateway in .NET using YARP. Add authentication, per-route authorization policies, and pass user identity to backend services.
Horizontally Scaling ASP.NET Core APIs With YARP Load Balancing
Learn how to scale ASP.NET Core APIs horizontally using YARP load balancing, with policies, health checks, and a full Program.cs setup explained simply.
YARP as an API Gateway in .NET: A Beginner's Guide
Learn how to use YARP as an API gateway in .NET 10. Routes, clusters, load balancing, health checks, auth, and transforms explained in simple, friendly steps.