Sitemap

YARP — Reverse Proxying the .NET Way

7 min readJan 13, 2026

--

I’m not sure how many people have heard of Yet Another Reverse Proxy (YARP) today, but it feels like a bit of a hidden gem in the Microsoft ecosystem. Although it’s often perceived as new, YARP has actually been around for about five years.

YARP positions itself as a highly customizable reverse proxy built on .NET. Translated from marketing language, it’s essentially a NuGet package that lets you route requests, handle load balancing, and manage authentication.

Compared to Nginx or Traefik, YARP is more developer-friendly for .NET developers because it keeps you within the .NET ecosystem.

Press enter or click to view image in full size
Photo by U. Storsberg on Unsplash

How YARP works

Let’s assume you have two APIs:

  • Catalog API running locally at https://localhost:5001/api/catalog
  • Orders API running locally at https://localhost:5002/api/orders

And you want a single entry point that exposes:

  • /api/catalog/* that redirects all traffic to the Catalog API
  • /api/orders/* that redirects all traffic to the Orders API

You can create a new C# project with the following code in program.cs:

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

app.MapReverseProxy();

app.Run();

The configuration in appsettings.json is a bit more involved, but remains easy to follow:

{
"ReverseProxy": {
"Routes": {
"catalog-route": {
"ClusterId": "catalog-cluster",
"Match": {
"Path": "/api/catalog/{**catch-all}"
}
},
"orders-route": {
"ClusterId": "orders-cluster",
"Match": {
"Path": "/api/orders/{**catch-all}"
}
}
},
"Clusters": {
"catalog-cluster": {
"Destinations": {
"catalog-api": {
"Address": "https://localhost:5001/"
}
}
},
"orders-cluster": {
"Destinations": {
"orders-api": {
"Address": "https://localhost:5002/"
}
}
}
}
}
}

That’s it. This configuration defines two reverse proxy routes: one for the catalog API and one for the orders API. Incoming requests matching /api/catalog/* are forwarded to the catalog-cluster, which routes traffic to a single destination running at https://localhost:5001. Similarly, requests matching /api/orders/* are sent to the orders-cluster, forwarding them to https://localhost:5002. Each route is explicitly mapped to its corresponding backend service.

YARP can also handle concerns that typically fall to either API gateways or infrastructure. YARP can handle rate limiting, retries, timeouts, circuit breakers, health checks, etc.

.NET-first productivity

Since YARP runs inside ASP.NET Core, you can stay in your comfort zone and use familiar tools, languages, and libraries. You can use dependency injection, logging, configuration, and middleware just like in any other ASP.NET Core application. There is no need to learn the NGINX or Traefik way of doing things.

YARP is also easily extensible in C#. You can add custom transforms, load-balancing strategies, or authentication logic using regular .NET code. This level of customization is difficult to achieve with traditional reverse proxies. While NGINX offers Lua scripting, it introduces yet another language to learn.

Aspire and YARP

Speaking of YARP being .NET-native, it’s worth mentioning that YARP is also a first-class citizen in Aspire. You can use the official Docker image and Aspire’s fluent APIs to define and configure a YARP gateway directly from your Aspire AppHost project.

Let’s imagine you have this endpoint in your Catalog API.

app.MapGet("categories", () =>
{
var categories = new[]
{
"1Electronics", "Books", "Clothing", "Home & Garden", "Toys", "Sports"
};
return Results.Ok(categories);
});

In the Aspire host:

//program.cs
var catalogApi = builder.AddProject<Projects.Catalog_API>("catalog-api");
var ordersApi = builder.AddProject<Projects.Orders_API>("orders-api");

// Gateway (containerized YARP resource managed by Aspire)
builder.AddYarp("gateway")
.WithHostPort(5002) // optional: expose gateway on http://localhost:5002
.WithConfiguration(yarp =>
{
// /api/catalog/* -> catalog-api
yarp.AddRoute("/api/catalog/{**catch-all}", catalogApi)
.WithTransformPathRemovePrefix("/api/catalog");
// /api/orders/* -> orders-api
yarp.AddRoute("/api/orders/{**catch-all}", ordersApi)
.WithTransformPathRemovePrefix("/api/orders");
});

With this code, you don’t need a separate cs project that contains the proxy setup and corresponding app settings. You can set everything via Aspire configuration methods (yarp.AddRoute).

It’s really handy since the yarp.AddRoute method accepts catalogApi and ordersApi directly, meaning service discovery is handled for you. You don’t have to worry about hard-coding ports or keeping endpoint configurations in sync.

Aspire Yarp also supports external services and other transformations. See the Aspire Yarp integration page to learn more.

Limitation of Aspire integration

One of the main limitations of Aspire YARP integration is that it uses a containerized version of YARP. This means that you cannot extend YARP with custom C# code since the YARP instance runs in a separate container. If you need to add custom logic, you have to create your own YARP project and manage it separately. So, for example, if you want to use an aggregate endpoint, Aspire doesn’t help you.

YARP and Kubernetes

And now a question that may come to mind for some of you: can YARP replace a Kubernetes(K8S) ingress?

Technically, it’s possible. Microsoft even has a YARP Kubernetes Ingress Controller module, but it’s shipped separately and is in preview (as of January 2026). I’d not recommend starting to use it right now.

For now, in the K8S context, NGINX should handle TLS termination, host/path routing, and load balancing. On the other hand, YARP should focus on authentication flows, tenant-aware logic, and API aggregation.

I realize this somewhat contradicts my earlier point that YARP can also handle routing and load balancing. However, when compared to Kubernetes Ingress, it’s still too early to rely on YARP for these responsibilities in a K8S environment.

That said, for smaller projects or setups without Kubernetes, using YARP as a full-featured reverse proxy may be a reasonable option.

My final point on using YARP in Kubernetes is that, as a .NET app, YARP fits naturally into the K8S ecosystem. You can rely on standard ASP.NET Core practices for configuration, logging, and monitoring, while fully leveraging the same Kubernetes features you’d use for any other ASP.NET Core app.

Performance

Last few weeks, I spent some time playing with the Rust language, so when I started writing this blog post, I immediately thought: “Reverse proxy in the runtime with managed memory. This couldn’t be right, could it”?

Regarding performance, the team behind YARP, not surprisingly, claims that YARP is highly optimised and very performant.

More surprisingly, Microsoft is doing dogfooding, as YARP + Kestrel is already used in Azure Active Directory, Dynamics 365, and Azure App Service. In Azure App Service, YARP + Kestrel replaced the original IIS and HTTP.sys in 2022. It seems that Microsoft believes in YARP.

On the other hand, as Milan Jovanović shows in this blog post, NGINX is still more performant.

I’m not surprised. YARP seems genuinely fast enough for most production setups and is built on a very optimized stack, but you shouldn’t expect it to beat NGINX in these types of microbenchmarks. No matter how good YARP is, it will always have more overhead than a C-based proxy server like NGINX.

Note: Despite this limitation, the YARP team intends to publish a migration guide to help teams move from NGINX to YARP.

YARP and enterprise

I think that YARP really shines in enterprise settings and for complex projects. It’s not a coincidence that YARP originated because many teams at Microsoft were creating their own internal reverse proxies.

YARP encourages developers to treat the proxy as an application component that can contain business logic. The following two examples demonstrate this in practice:

Centralize auth logic

You can create a proxy project with all AddAuthorization and UseAuthentication as you usually do, and then just set the proxy like that:

app.MapReverseProxy(proxyPipeline =>
{
// Require authentication for all proxied requests
proxyPipeline.UseAuthorization();
});

You can now leave downstream services unauthenticated, but honestly, this is a pattern I distrust. Relying on a single authentication choke point assumes perfect network isolation and no human mistakes in the future.

I believe the correct way is to validate the external token, then create an internal JWT for service-to-service communication with only the necessary claims and a short TTL.

API gateway pattern

Another nice example is the API gateway pattern. You can easily create aggregate endpoints, like this:

// Custom endpoint that calls BOTH APIs and returns one JSON
app.MapGet("/api/summary", async (
HttpContext ctx,
IHttpClientFactory httpFactory) =>
{
var http = httpFactory.CreateClient();
// Example downstream calls
var catalogUrl = "https://localhost:5001/api/catalog/...";
var ordersUrl = "https://localhost:5002/api/orders/...";

var catalogTask = http.GetStringAsync(catalogUrl, ctx.RequestAborted);
var ordersTask = http.GetStringAsync(ordersUrl, ctx.RequestAborted);

await Task.WhenAll(catalogTask, ordersTask);

return Results.Json(new
{
catalog = await catalogTask,
orders = await ordersTask,
composedAtUtc = DateTime.UtcNow
});
});

Proxy That Can Do Everything

However, bear in mind that a C# project utilizing YARP can be easily converted to a “Proxy That Can Do Everything”. I always see reverse proxies as infrastructure components that should be fast and simple. But with YARP, you can easily end up with a massive project that handles routing, authentication, rate limiting, logging, fine-grained security, transformations, and a lot of business logic via aggregate endpoints.

Don’t forget that scope creep is a developer’s nemesis and lurks everywhere, including in programmable proxy code.

A geography metaphor to save us.

In the network industry, there is a well-known concept of north–south and east–west traffic. North–south traffic refers to communication between clients and servers (in and out of the data center), while east–west traffic refers to communication between services within the data center.

When designing systems with YARP, I strongly believe you should pick one of two architectural patterns:

  • The first is Edge YARP (north–south traffic), where YARP acts as the public entry point into the system, handling responsibilities such as TLS termination, coarse-grained routing, and basic request/response transformations.
  • The second option is Internal YARP (east–west traffic), which is optional and applies inside the system, in front of a group of services that require custom routing rules, traffic shaping, or support for progressive delivery techniques such as canary or blue-green deployments.

You can, of course, have both edge and internal YARP proxies in your system, but they should not live within a single C# project. That said, I would consider breaking this rule if performance constraints leave no other reasonable option, and for smaller projects.

Conclusion

Yarp is super powerful. In theory, it can replace many standard components in your cluster, such as DDoS protection, bot mitigation, or even a custom Web Application Firewall. But I’d not recommend doing it. There are many battle-proof solutions that can handle this functionality way better than a custom solution you might want to build and add to your proxy.

I also don’t see YARP as a replacement for NGINX or Kubernetes Ingress. Those tools are better at being fast, boring, and reliable infrastructure. YARP is a .NET application that happens to be a reverse proxy.

It’s easy to keep adding “just one more thing” until your YARN proxy owns routing, security, retries, and business logic. At that point, you no longer have a proxy, but a fragile “god proxy”.

So to be on the safe side, give YARP a clear role in your system, and keep it small.

--

--