YARP vs Nginx - A Quick Performance Comparison

YARP vs Nginx - A Quick Performance Comparison

7 min read ·

You're Invited: Watch Postman's Keynote Talk, APIs in the Age of Autonomous Agents Can't be at POST/CON this year? Don't worry—we've got you! Join Postman CEO Abhinav Asthana & Head of Marketing Justine Davis for this livestream keynote event on June 4 at 9:00 AM PT as they talk through how APIs have evolved from simple endpoints to intelligent instructions for autonomous agents. Learn how leading teams are standardizing design, automating testing, and scaling distribution in an AI-driven world. Get notified and tune in live on YouTube

Nick Chapsas' Dometrain is celebrating 2 years of teaching .NET developers, and they are offering their "From Zero to Hero: REST APIs in .NET" course for free. Until the end of June, use the link below, and the course is yours to keep for 1 month. Get it for free.

When you're building .NET applications, choosing the right reverse proxy can make a huge difference. Two popular options keep coming up: Microsoft's YARP (Yet Another Reverse Proxy) and the tried-and-true Nginx.

Here's the thing - everyone talks about which one is "better," but rarely do you see actual numbers. So I decided to put both through the same tests and see what happens.

I'll test both proxies using the exact same API, same hardware, and same load testing approach. No bias, just data.

The Test API

I kept the test API super simple on purpose. This way, we're measuring proxy performance, not how fast the backend can process complex requests:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/hello", () =>
{
    return Results.Ok("Hello world!");
});

app.Run();

This basic endpoint means we're testing the proxy itself, not waiting for complex business logic to run.

YARP Configuration

YARP is pretty nice to work with if you're already in the .NET world. The setup is straightforward:

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddReverseProxy()
    .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));

var app = builder.Build();
app.MapReverseProxy();
app.Run();

It's equally simple to configure load balancing or authentication.

The routing setup is clean and uses a **catch-all pattern to forward everything:

{
  "ReverseProxy": {
    "Routes": {
      "default": {
        "ClusterId": "hello",
        "Match": { "Path": "{**catch-all}" }
      }
    },
    "Clusters": {
      "hello": {
        "Destinations": {
          "destination1": {
            "Address": "http://hello.api:8080"
          }
        }
      }
    }
  }
}

Nginx Setup

For Nginx, I went with Docker to keep things simple. The configuration does the same job as YARP:

nginx.proxy:
  image: nginx:alpine
  ports:
    - '3001:80'
  volumes:
    - ./nginx-proxy.conf:/etc/nginx/nginx.conf:ro
  depends_on:
    - hello.api

The Nginx config does exactly what YARP does - just with different syntax:

events {}

http {
    upstream backend {
        server hello.api:8080;
    }

    server {
        listen 80;
        location / {
            proxy_pass http://backend;
        }
    }
}

Full Docker Compose Setup

Here's the complete docker-compose.yml that ties everything together:

services:
  hello.api:
    image: ${DOCKER_REGISTRY-}helloapi
    build:
      context: .
      dockerfile: Hello.Api/Dockerfile

  yarp.proxy:
    image: ${DOCKER_REGISTRY-}yarpproxy
    build:
      context: .
      dockerfile: Yarp.Proxy/Dockerfile
    ports:
      - 3000:8080
    depends_on:
      - hello.api

  nginx.proxy:
    image: nginx:alpine
    ports:
      - '3001:80'
    volumes:
      - ./nginx-proxy.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - hello.api

Load Testing with k6

I used k6 to hit both proxies with the same load patterns. I repeated the test with different numbers of virtual users (VUs) to see how each proxy handles increasing traffic. This keeps things fair - same test, same conditions:

YARP Test Script:

import http from 'k6/http';
import { check } from 'k6';

export let options = {
  scenarios: {
    yarp: {
      executor: 'per-vu-iterations',
      vus: 200, // 10, 50, 100, 200
      iterations: 1000,
      exec: 'testYarp',
      startTime: '0s'
    }
  }
};

export function testYarp() {
  let res = http.get('http://localhost:3000/hello');
  check(res, {
    'YARP: status 200': (r) => r.status === 200
  });
}

Nginx Test Script:

import http from 'k6/http';
import { check } from 'k6';

export let options = {
  scenarios: {
    nginx: {
      executor: 'per-vu-iterations',
      vus: 200, // 10, 50, 100, 200
      iterations: 1000,
      exec: 'testNginx',
      startTime: '0s'
    }
  }
};

export function testNginx() {
  let res = http.get('http://localhost:3001/hello');
  check(res, {
    'NGINX: status 200': (r) => r.status === 200
  });
}

Performance Results

Here's where things get interesting. The numbers show a clear pattern:

| VUs  | YARP RPS | NGINX RPS | YARP p90 Latency (ms) | NGINX p90 Latency (ms) | YARP p95 Latency (ms) | NGINX p95 Latency (ms) |
|------|----------|-----------|-----------------------|------------------------|-----------------------|------------------------|
| 10   | 12692    | 9756      | 1.04                  | 1.10                   | 1.06                  | 1.52                   |
| 50   | 27080    | 10614     | 2.70                  | 5.23                   | 3.18                  | 5.68                   |
| 100  | 32432    | 10324     | 4.66                  | 10.61                  | 5.43                  | 10.96                  |
| 200  | 36662    | 10169     | 7.77                  | 21.23                  | 8.81                  | 21.92                  |

Request per second (RPS) is how many requests each proxy handled per second.

YARP vs Nginx RPS comparison

p90 latency is the time it took for 90% of requests to complete.

YARP vs Nginx p90 latency comparison

p95 latency is the time it took for 95% of requests to complete.

YARP vs Nginx p95 latency comparison

Throughput Analysis

YARP really shines here. It handles way more requests - almost 3.6x more at 200 users. What's cool is how it scales up as you add more load. Nginx stays pretty much flat around 10k requests per second, but YARP keeps climbing from 12k all the way to 36k.

Latency Comparison

The latency story is even more impressive for YARP. At 200 users, YARP keeps response times under 8ms while Nginx hits 21ms. That's a big difference when you're trying to keep your app fast.

Hold Up - That's Not Fair

Looking at these results, we're missing something important: this comparison isn't fair to Nginx.

The default Nginx configuration I used is fine for basic setups, but it's not optimized for high-throughput scenarios. Nginx uses conservative defaults that work everywhere but don't push performance limits.

So let me fix the Nginx configuration and re-run the tests.

Here's the updated Nginx config with some tweaks to improve performance:

worker_processes auto;

events {
    worker_connections 65536;
    multi_accept on;
    use epoll;
}

http {
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 30;
    keepalive_requests 1000;
    types_hash_max_size 4096;

    upstream backend {
        server hello.api:8080;
        keepalive 512;
    }

    server {
        listen 80;

        location / {
            proxy_pass http://backend;
            proxy_http_version 1.1;
            proxy_set_header Connection "";
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
    }
}

Performance Results - After Tuning

Here are the results after re-running the tests:

| VUs  | YARP RPS | NGINX RPS | YARP p90 Latency (ms) | NGINX p90 Latency (ms) | YARP p95 Latency (ms) | NGINX p95 Latency (ms) |
|------|----------|-----------|-----------------------|------------------------|-----------------------|------------------------|
| 10   | 12692    | 17572     | 1.04                  | 0.58                   | 1.06                  | 0.74                   |
| 50   | 27080    | 36687     | 2.70                  | 1.81                   | 3.18                  | 2.09                   |
| 100  | 32432    | 43289     | 4.66                  | 3.18                   | 5.43                  | 3.88                   |
| 200  | 36662    | 46850     | 7.77                  | 6.34                   | 8.81                  | 7.72                   |
YARP vs Nginx RPS comparison YARP vs Nginx p90 latency comparison YARP vs Nginx p95 latency comparison

Throughput Analysis

Now this is more interesting. Nginx actually edges out YARP in raw throughput - hitting 46k requests per second vs YARP's 36k at 200 users. Both proxies scale well as load increases, but Nginx shows why it's been the go-to choice for high-traffic sites.

Latency Comparison

The latency story is pretty close. At lower loads, Nginx actually has better response times. At 200 users, both proxies keep response times reasonable - YARP at 7.77ms and Nginx at 6.34ms for p90 latency. The difference isn't huge either way.

Key Takeaways

Configuration matters more than you think. The initial results showed YARP crushing Nginx, but that was with Nginx's conservative defaults. Once properly tuned, Nginx shows why it's been powering the internet for years.

Nginx wins on raw performance. With proper configuration, Nginx handles more requests and keeps latency slightly lower. That extra throughput matters when you're dealing with serious traffic.

YARP offers better integration. Even though Nginx edges out performance, YARP feels natural in .NET projects. Same configuration style, same patterns, same tooling. Sometimes that developer experience is worth more than a few extra requests per second.

Always tune your tools. This whole exercise shows why benchmarks with default configs can be misleading. If you're choosing between these two, make sure you're comparing optimized configurations, not defaults.

The choice isn't as clear-cut as I initially thought. Nginx wins on pure performance, but YARP wins on .NET integration. Pick based on what matters more for your specific situation.


Whenever you're ready, there are 4 ways I can help you:

  1. (NEW) Pragmatic REST APIs: You will learn how to build production-ready REST APIs using the latest ASP.NET Core features and best practices. It includes a fully functional UI application that we'll integrate with the REST API.
  2. Pragmatic Clean Architecture: Join 4,000+ students in this comprehensive course that will teach you the system I use to ship production-ready applications using Clean Architecture. Learn how to apply the best practices of modern software architecture.
  3. Modular Monolith Architecture: Join 2,000+ engineers in this in-depth course that will transform the way you build modern systems. You will learn the best practices for applying the Modular Monolith architecture in a real-world scenario.
  4. Patreon Community: Join a community of 1,000+ engineers and software architects. You will also unlock access to the source code I use in my YouTube videos, early access to future videos, and exclusive discounts for my courses.

Become a Better .NET Software Engineer

Join 66,000+ engineers who are improving their skills every Saturday morning.