| name | yarp |
| description | USE FOR: Building high-performance reverse proxies, API gateways, and load balancers using YARP (Yet Another Reverse Proxy) from Microsoft. Use when you need customizable request routing, load balancing, header transforms, session affinity, health checks, and dynamic configuration for proxying traffic to backend services.
DO NOT USE FOR: Simple API gateway features like request aggregation and built-in rate limiting (use Ocelot), service mesh capabilities like mTLS and circuit breaking (use Istio or Linkerd), or CDN-style edge caching (use a CDN like Cloudflare or Azure Front Door).
|
| license | MIT |
| metadata | {"displayName":"YARP","author":"Tyler-R-Kendrick","version":"1.0.0"} |
| compatibility | ["claude","copilot","cursor"] |
| references | [{"title":"YARP Documentation","url":"https://learn.microsoft.com/en-us/aspnet/core/fundamentals/servers/yarp/getting-started"},{"title":"YARP GitHub Repository","url":"https://github.com/dotnet/yarp"},{"title":"Yarp.ReverseProxy NuGet Package","url":"https://www.nuget.org/packages/Yarp.ReverseProxy"}] |
YARP
Overview
YARP (Yet Another Reverse Proxy) is a highly customizable reverse proxy library from Microsoft, built on ASP.NET Core. It provides configurable routing, load balancing (round-robin, random, power-of-two-choices, least-requests), health checks (active and passive), session affinity, header/path/query transforms, and request forwarding. YARP can be configured through JSON files, code, or dynamic configuration providers (service discovery, databases, APIs). It integrates into the ASP.NET Core middleware pipeline, enabling custom middleware to run before, during, or after proxying. YARP is suitable for building API gateways, BFF (Backend for Frontend) layers, multi-tenant routers, and load balancers within .NET applications.
Basic Setup with Configuration
Configure YARP from appsettings.json with routes and clusters.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
var app = builder.Build();
app.MapReverseProxy();
app.Run();
{
"ReverseProxy": {
"Routes": {
"products-route": {
"ClusterId": "products-cluster",
"Match": {
"Path": "/api/products/{**catch-all}"
},
"Transforms": [
{ "PathRemovePrefix": "/api/products" },
{ "RequestHeader": "X-Forwarded-Prefix", "Set": "/api/products" }
]
},
"orders-route": {
"ClusterId": "orders-cluster",
"AuthorizationPolicy": "authenticated",
Programmatic Configuration
Configure routes and clusters in code for dynamic scenarios.
using Yarp.ReverseProxy.Configuration;
var builder = WebApplication.CreateBuilder(args);
var routes = new[]
{
new RouteConfig
{
RouteId = "api-route",
ClusterId = "api-cluster",
Match = new RouteMatch
{
Path = "/api/{**catch-all}"
},
Transforms = new[]
{
new Dictionary<string, string>
{
["PathRemovePrefix"] = "/api"
},
new Dictionary<string, string>
{
["RequestHeadersCopy"] = "true"
},
new Dictionary<string, string>
{
["RequestHeader"] = "X-Gateway",
["Set"] = "yarp-v1"
}
}
}
};
var clusters = new[]
{
new ClusterConfig
{
ClusterId = "api-cluster",
LoadBalancingPolicy = "PowerOfTwoChoices",
HealthCheck = new HealthCheckConfig
{
Active = new ActiveHealthCheckConfig
{
Enabled = true,
Interval = TimeSpan.FromSeconds(15),
Timeout = TimeSpan.FromSeconds(5),
Path = "/health"
},
Passive = new PassiveHealthCheckConfig
{
Enabled = ,
Policy = ,
ReactivationPeriod = TimeSpan.FromMinutes()
}
},
SessionAffinity = SessionAffinityConfig
{
Enabled = ,
Policy = ,
AffinityKeyName =
},
Destinations = Dictionary<, DestinationConfig>
{
[] = () { Address = },
[] = () { Address = },
[] = () { Address = }
}
}
};
builder.Services.AddReverseProxy()
.LoadFromMemory(routes, clusters);
app = builder.Build();
app.MapReverseProxy();
app.Run();
Custom Middleware in the Proxy Pipeline
Add middleware that runs within the YARP proxy pipeline for request/response manipulation.
using Yarp.ReverseProxy.Model;
using System.Diagnostics;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
var app = builder.Build();
app.MapReverseProxy(proxyPipeline =>
{
proxyPipeline.Use(async (context, next) =>
{
var proxyFeature = context.GetReverseProxyFeature();
var clusterId = proxyFeature.Route.Config.ClusterId;
var stopwatch = Stopwatch.StartNew();
var correlationId = context.Request.Headers["X-Correlation-Id"].FirstOrDefault()
?? Guid.NewGuid().ToString();
context.Request.Headers["X-Correlation-Id"] = correlationId;
await next();
stopwatch.Stop();
context.Response.Headers["X-Proxy-Latency"] = $"{stopwatch.ElapsedMilliseconds}ms";
context.Response.Headers["X-Served-By"] = clusterId;
context.Response.Headers["X-Correlation-Id"] = correlationId;
if (stopwatch.ElapsedMilliseconds > 2000)
{
var logger = context.RequestServices.GetRequiredService<ILogger<Program>>();
logger.LogWarning(
"Slow proxy response: {Cluster} took {Duration}ms for {Path}",
clusterId,
stopwatch.ElapsedMilliseconds,
context.Request.Path);
}
});
proxyPipeline.UseSessionAffinity();
proxyPipeline.UseLoadBalancing();
proxyPipeline.UsePassiveHealthChecks();
});
app.Run();
Dynamic Configuration Provider
Implement a custom configuration provider that loads routes from a database or API.
using Yarp.ReverseProxy.Configuration;
using Microsoft.Extensions.Primitives;
using System.Collections.Concurrent;
namespace MyApp.Proxy;
public class DatabaseProxyConfigProvider : IProxyConfigProvider, IDisposable
{
private readonly IServiceScopeFactory _scopeFactory;
private readonly ILogger<DatabaseProxyConfigProvider> _logger;
private readonly CancellationTokenSource _cts = new();
private InMemoryConfig _config;
public DatabaseProxyConfigProvider(
IServiceScopeFactory scopeFactory,
ILogger<DatabaseProxyConfigProvider> logger)
{
_scopeFactory = scopeFactory;
_logger = logger;
_config = new InMemoryConfig(
Array.Empty<RouteConfig>(),
Array.Empty<ClusterConfig>());
_ = RefreshConfigPeriodically(_cts.Token);
}
public IProxyConfig GetConfig() => _config;
private async Task RefreshConfigPeriodically(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
try
{
await RefreshAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to refresh proxy configuration");
}
await Task.Delay(TimeSpan.FromMinutes(1), cancellationToken);
}
}
{
scope = _scopeFactory.CreateScope();
dbContext = scope.ServiceProvider.GetRequiredService<ProxyDbContext>();
dbRoutes = dbContext.ProxyRoutes.ToListAsync();
dbClusters = dbContext.ProxyClusters
.Include(c => c.Destinations)
.ToListAsync();
routes = dbRoutes.Select(r => RouteConfig
{
RouteId = r.RouteId,
ClusterId = r.ClusterId,
Match = RouteMatch { Path = r.MatchPath }
}).ToList();
clusters = dbClusters.Select(c => ClusterConfig
{
ClusterId = c.ClusterId,
LoadBalancingPolicy = c.LoadBalancingPolicy,
Destinations = c.Destinations.ToDictionary(
d => d.DestinationId,
d => DestinationConfig { Address = d.Address })
}).ToList();
oldConfig = _config;
_config = InMemoryConfig(routes, clusters);
oldConfig.SignalChange();
_logger.LogInformation(
,
routes.Count, clusters.Count);
}
=> _cts.Cancel();
}
:
{
CancellationTokenSource _cts = ();
{
Routes = routes;
Clusters = clusters;
ChangeToken = CancellationChangeToken(_cts.Token);
}
IReadOnlyList<RouteConfig> Routes { ; }
IReadOnlyList<ClusterConfig> Clusters { ; }
IChangeToken ChangeToken { ; }
=> _cts.Cancel();
}
builder.Services.AddSingleton<IProxyConfigProvider, DatabaseProxyConfigProvider>();
builder.Services.AddReverseProxy();
YARP vs Other Reverse Proxy Solutions
| Feature | YARP | Ocelot | NGINX | Envoy |
|---|
| Language | C# / .NET | C# / .NET | C / Lua | C++ |
| Configuration | JSON / code / custom provider | JSON file | nginx.conf | YAML / xDS |
| Load balancing | Round-robin, random, power-of-two, least-requests | Round-robin, least-conn | Round-robin, least-conn, IP hash | Round-robin, least-req, ring hash |
| Health checks | Active + passive | Not built-in | Active (upstream) | Active + passive + outlier |
| Session affinity | Cookie, header, custom | Not built-in | IP hash, sticky cookie | Cookie, header, ring hash |
| Transforms | Path, header, query | Not built-in | Header manipulation | Header, path, Lua filters |
| Dynamic config | Custom IProxyConfigProvider | JSON reload | Reload signal | xDS API |
| Custom middleware | ASP.NET Core pipeline | Delegating handlers | Lua scripting | WASM / C++ filters |
| Request aggregation | Not built-in | Built-in | Not built-in | Not built-in |
| Performance | Very high (.NET optimized) | Moderate | Very high (C/epoll) | Very high (C++/epoll) |
Best Practices
-
Enable both active and passive health checks on clusters with multiple destinations, setting Active.Enabled = true with a health endpoint path and Passive.Enabled = true with TransportFailureRate policy, so that unhealthy destinations are detected both proactively (periodic probes) and reactively (failed request responses).
-
Use PowerOfTwoChoices load balancing for most production scenarios instead of RoundRobin, because it selects the least-loaded destination from two randomly chosen candidates, providing better distribution than round-robin when destination response times vary without the overhead of tracking all destination loads.
-
Configure session affinity with Cookie policy when backend services maintain in-memory session state (shopping carts, wizard flows), setting AffinityKeyName to a unique cookie name, so that subsequent requests from the same client are routed to the same destination for the duration of the session.
-
Use transforms to strip route prefixes with PathRemovePrefix and add forwarded headers with RequestHeader transforms, rather than modifying backend services to handle gateway prefixes, because transforms keep backend services unaware of the gateway's URL structure.
-
Implement IProxyConfigProvider for dynamic configuration from databases, service registries, or APIs when routes and clusters change at runtime, calling SignalChange() on the CancellationChangeToken to notify YARP of configuration updates without restarting the application.
-
Add custom middleware to the proxy pipeline using MapReverseProxy(pipeline => ...) for cross-cutting concerns (logging, metrics, rate limiting, authentication validation), placing it before UseLoadBalancing() so it runs before destination selection and can short-circuit requests.
-
Set Active.Interval and Active.Timeout on health checks to appropriate values for your environment (e.g., 15-second interval, 5-second timeout for local services; 30-second interval, 10-second timeout for remote services), because aggressive intervals increase health check traffic while long intervals delay detection of failed destinations.
-
Use route ordering with property when multiple routes could match the same request path, because YARP evaluates routes in ascending order and stops at the first match; without explicit ordering, the matching route depends on configuration source order which may vary between deployments.