YARP — Building API Gateway for Microservices on .NET 10
Posted on: 4/24/2026 8:11:50 PM
Table of contents
- 1. What is YARP and Why Should You Use It?
- 2. Installation and Basic Configuration
- 3. Load Balancing — Intelligent Traffic Distribution
- 4. Centralized Authentication & Authorization
- 5. Rate Limiting — Protecting Your Backend
- 6. Request Transforms — Modifying Requests and Responses
- 7. Backend-For-Frontend (BFF) Pattern
- 8. Canary Deployment & A/B Testing
- 9. Observability — Monitoring and Tracing
- 10. Dynamic Configuration — Runtime Config Changes
- 11. YARP vs Other Solutions
- 12. Production Checklist
- 13. Production Architecture Overview
- Conclusion
In microservices architecture, the API Gateway is an essential component — serving as the single entry point for all client requests, handling cross-cutting concerns like authentication, rate limiting, load balancing, and request transformation. Instead of relying on external solutions like Kong or Nginx, the .NET ecosystem has YARP (Yet Another Reverse Proxy) — a high-performance reverse proxy developed by Microsoft and used internally for Azure, Bing, and Microsoft 365.
1. What is YARP and Why Should You Use It?
YARP is an open-source reverse proxy library built on ASP.NET Core. Unlike traditional API Gateways that operate as standalone applications, YARP is embedded directly into the ASP.NET Core pipeline — allowing you to leverage the entire existing middleware ecosystem.
graph LR
A[Client Mobile/Web/SPA] -->|HTTPS| B[YARP API Gateway
.NET 10]
B -->|Route /api/orders/*| C[Order Service
Port 5001]
B -->|Route /api/products/*| D[Product Service
Port 5002]
B -->|Route /api/users/*| E[User Service
Port 5003]
B -->|Route /api/notifications/*| F[Notification Service
Port 5004]
style B fill:#e94560,stroke:#fff,color:#fff
style C fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style D fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style E fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style F fill:#f8f9fa,stroke:#e94560,color:#2c3e50
Why not Ocelot?
Ocelot was once the popular choice for .NET API Gateways, but the project has been slow to update and hasn't kept pace with new ASP.NET Core features. YARP is officially supported by Microsoft, updated alongside each new .NET release, and has proven its performance in production at Microsoft scale.
2. Installation and Basic Configuration
2.1. Project Setup
dotnet new webapi -n ApiGateway
cd ApiGateway
dotnet add package Yarp.ReverseProxy
2.2. Program.cs Configuration
var builder = WebApplication.CreateBuilder(args);
// Register YARP services
builder.Services.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
var app = builder.Build();
// Map reverse proxy into pipeline
app.MapReverseProxy();
app.Run();
2.3. Routes and Clusters in appsettings.json
{
"ReverseProxy": {
"Routes": {
"orders-route": {
"ClusterId": "orders-cluster",
"Match": {
"Path": "/api/orders/{**catch-all}"
},
"Transforms": [
{ "PathRemovePrefix": "/api/orders" }
]
},
"products-route": {
"ClusterId": "products-cluster",
"Match": {
"Path": "/api/products/{**catch-all}"
},
"Transforms": [
{ "PathRemovePrefix": "/api/products" }
]
}
},
"Clusters": {
"orders-cluster": {
"LoadBalancingPolicy": "RoundRobin",
"Destinations": {
"orders-1": { "Address": "https://localhost:5001" },
"orders-2": { "Address": "https://localhost:5011" }
}
},
"products-cluster": {
"Destinations": {
"products-1": { "Address": "https://localhost:5002" }
}
}
}
}
}
Two Core Concepts
Routes define pattern matching for incoming requests (path, headers, query string). Clusters represent groups of backend services receiving requests — each cluster can have multiple destinations for load balancing.
3. Load Balancing — Intelligent Traffic Distribution
YARP supports multiple load balancing algorithms, allowing you to choose the right strategy for each service type.
| Policy | Description | Best For |
|---|---|---|
| RoundRobin | Distributes evenly across instances | Identically configured instances |
| LeastRequests | Routes to instance with fewest active requests | Requests with varying processing times |
| Random | Random selection | Testing, development |
| PowerOfTwoChoices | Picks better of two random instances | Balance between random and optimal |
| FirstAlphabetical | Always routes to first instance | Active-passive failover |
3.1. Health Checks — Automatic Failure Detection
YARP supports both Active and Passive health checks to automatically remove unhealthy instances from the pool.
{
"orders-cluster": {
"LoadBalancingPolicy": "RoundRobin",
"HealthCheck": {
"Active": {
"Enabled": true,
"Interval": "00:00:10",
"Timeout": "00:00:05",
"Policy": "ConsecutiveFailures",
"Path": "/health"
},
"Passive": {
"Enabled": true,
"Policy": "TransportFailure",
"ReactivationPeriod": "00:00:30"
}
},
"Destinations": {
"orders-1": { "Address": "https://localhost:5001" },
"orders-2": { "Address": "https://localhost:5011" }
}
}
}
sequenceDiagram
participant C as Client
participant G as YARP Gateway
participant S1 as Service Instance 1
participant S2 as Service Instance 2
Note over G: Active Health Check every 10s
G->>S1: GET /health
S1-->>G: 200 OK ✓
G->>S2: GET /health
S2-->>G: 503 Error ✗
Note over G: Mark S2 unhealthy
C->>G: GET /api/orders/123
G->>S1: Forward request
S1-->>G: 200 Response
G-->>C: 200 Response
Note over G: 30s later — reactivation
G->>S2: GET /health
S2-->>G: 200 OK ✓
Note over G: S2 back to healthy
4. Centralized Authentication & Authorization
One of the biggest advantages of an API Gateway is centralizing authentication at a single point, freeing downstream services from having to validate tokens themselves.
var builder = WebApplication.CreateBuilder(args);
// Configure JWT Authentication
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "https://auth.example.com";
options.Audience = "api-gateway";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
ValidateIssuer = true,
ValidateAudience = true,
ClockSkew = TimeSpan.Zero
};
});
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AdminOnly", policy =>
policy.RequireRole("admin"));
options.AddPolicy("ReadAccess", policy =>
policy.RequireAuthenticatedUser());
});
builder.Services.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapReverseProxy();
app.Run();
Applying policies to individual routes:
{
"Routes": {
"admin-route": {
"ClusterId": "admin-cluster",
"AuthorizationPolicy": "AdminOnly",
"Match": { "Path": "/api/admin/{**catch-all}" }
},
"public-route": {
"ClusterId": "products-cluster",
"AuthorizationPolicy": "anonymous",
"Match": { "Path": "/api/products/{**catch-all}" }
}
}
}
5. Rate Limiting — Protecting Your Backend
Since .NET 7, ASP.NET Core includes built-in Rate Limiting middleware. YARP leverages this directly through RateLimiterPolicy on each route.
builder.Services.AddRateLimiter(options =>
{
options.AddFixedWindowLimiter("fixed-policy", opt =>
{
opt.PermitLimit = 100;
opt.Window = TimeSpan.FromMinutes(1);
opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
opt.QueueLimit = 10;
});
options.AddSlidingWindowLimiter("sliding-policy", opt =>
{
opt.PermitLimit = 200;
opt.Window = TimeSpan.FromMinutes(1);
opt.SegmentsPerWindow = 4;
});
options.AddTokenBucketLimiter("token-bucket", opt =>
{
opt.TokenLimit = 50;
opt.ReplenishmentPeriod = TimeSpan.FromSeconds(10);
opt.TokensPerPeriod = 10;
opt.AutoReplenishment = true;
});
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
});
// In the pipeline
app.UseRateLimiter();
app.MapReverseProxy();
{
"Routes": {
"orders-route": {
"ClusterId": "orders-cluster",
"RateLimiterPolicy": "fixed-policy",
"Match": { "Path": "/api/orders/{**catch-all}" }
}
}
}
6. Request Transforms — Modifying Requests and Responses
YARP provides a powerful transform system to modify requests before forwarding and responses before returning to clients.
graph LR
A[Client Request] --> B[YARP Gateway]
B --> C{Transforms}
C --> D[Path Rewrite]
C --> E[Header Inject]
C --> F[Query String]
D --> G[Backend Service]
E --> G
F --> G
G --> H{Response Transforms}
H --> I[Header Remove]
H --> J[Body Transform]
I --> K[Client Response]
J --> K
style B fill:#e94560,stroke:#fff,color:#fff
style C fill:#2c3e50,stroke:#fff,color:#fff
style H fill:#2c3e50,stroke:#fff,color:#fff
6.1. Configuration-based Transforms
{
"Routes": {
"v2-products": {
"ClusterId": "products-v2-cluster",
"Match": { "Path": "/api/v2/products/{**remainder}" },
"Transforms": [
{ "PathPattern": "/products/{**remainder}" },
{ "RequestHeader": "X-Gateway", "Set": "YARP" },
{ "RequestHeader": "X-Request-Id", "Set": "{random-guid}" },
{ "ResponseHeaderRemove": "X-Internal-Debug" },
{ "X-Forwarded": "Set", "For": "Append", "Proto": "Set" }
]
}
}
}
6.2. Custom Transforms in Code
builder.Services.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"))
.AddTransforms(context =>
{
// Add correlation ID for distributed tracing
context.AddRequestTransform(transformContext =>
{
var correlationId = transformContext.HttpContext
.Request.Headers["X-Correlation-Id"]
.FirstOrDefault() ?? Guid.NewGuid().ToString();
transformContext.ProxyRequest.Headers
.Add("X-Correlation-Id", correlationId);
return ValueTask.CompletedTask;
});
// Log response status
context.AddResponseTransform(transformContext =>
{
var logger = transformContext.HttpContext
.RequestServices.GetRequiredService<ILogger<Program>>();
logger.LogInformation(
"Proxy response: {StatusCode} for {Path}",
transformContext.ProxyResponse?.StatusCode,
transformContext.HttpContext.Request.Path);
return ValueTask.CompletedTask;
});
});
7. Backend-For-Frontend (BFF) Pattern
YARP enables the BFF pattern — creating specialized gateways for each client type (mobile, web, third-party) by routing based on headers.
{
"Routes": {
"mobile-orders": {
"ClusterId": "mobile-bff-cluster",
"Match": {
"Path": "/api/orders/{**catch-all}",
"Headers": [
{
"Name": "X-Client-Type",
"Values": ["mobile"],
"Mode": "ExactHeader"
}
]
}
},
"web-orders": {
"ClusterId": "web-bff-cluster",
"Match": {
"Path": "/api/orders/{**catch-all}",
"Headers": [
{
"Name": "X-Client-Type",
"Values": ["web"],
"Mode": "ExactHeader"
}
]
}
}
}
}
graph TD
A[Mobile App] -->|X-Client-Type: mobile| G[YARP Gateway]
B[Web SPA] -->|X-Client-Type: web| G
C[Third-party] -->|API Key| G
G --> D[Mobile BFF
Optimized payload, less data]
G --> E[Web BFF
Full payload, rich UI]
G --> F[Partner API
Versioned, throttled]
D --> H[Order Service]
D --> I[User Service]
E --> H
E --> I
E --> J[Analytics Service]
F --> H
style G fill:#e94560,stroke:#fff,color:#fff
style D fill:#f8f9fa,stroke:#4CAF50,color:#2c3e50
style E fill:#f8f9fa,stroke:#2196F3,color:#2c3e50
style F fill:#f8f9fa,stroke:#ff9800,color:#2c3e50
8. Canary Deployment & A/B Testing
YARP supports weighted traffic distribution — the foundation for canary deployments and A/B testing without separate infrastructure.
builder.Services.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"))
.AddTransforms(context =>
{
context.AddRequestTransform(transformContext =>
{
// A/B testing based on user ID hash
var userId = transformContext.HttpContext.User
.FindFirst("sub")?.Value ?? "anonymous";
var bucket = Math.Abs(userId.GetHashCode()) % 100;
if (bucket < 10) // 10% traffic to new version
{
transformContext.ProxyRequest.Headers
.Add("X-Feature-Group", "canary");
}
return ValueTask.CompletedTask;
});
});
{
"Clusters": {
"products-cluster": {
"LoadBalancingPolicy": "RoundRobin",
"Destinations": {
"stable": {
"Address": "https://products-v1:5002",
"Metadata": { "Weight": "90" }
},
"canary": {
"Address": "https://products-v2:5012",
"Metadata": { "Weight": "10" }
}
}
}
}
}
9. Observability — Monitoring and Tracing
YARP integrates seamlessly with OpenTelemetry, providing end-to-end traces, metrics, and logs from gateway to backend.
builder.Services.AddOpenTelemetry()
.WithTracing(tracing =>
{
tracing
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddOtlpExporter(options =>
{
options.Endpoint = new Uri("http://otel-collector:4317");
});
})
.WithMetrics(metrics =>
{
metrics
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddMeter("Yarp.ReverseProxy")
.AddOtlpExporter();
});
builder.Logging.AddOpenTelemetry(logging =>
{
logging.AddOtlpExporter();
});
Built-in YARP Metrics
YARP automatically emits metrics via System.Diagnostics.Metrics: request count per route, P50/P95/P99 latency, proxy errors, and health check results. Prometheus/Grafana integration only requires adding AddPrometheusExporter().
10. Dynamic Configuration — Runtime Config Changes
Configuration doesn't always live in appsettings.json. YARP supports dynamic configuration from databases, Consul, Kubernetes, or any custom source.
public class DatabaseProxyConfigProvider : IProxyConfigProvider
{
private readonly IServiceProvider _serviceProvider;
private CancellationTokenSource _cts = new();
public DatabaseProxyConfigProvider(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public IProxyConfig GetConfig()
{
using var scope = _serviceProvider.CreateScope();
var dbContext = scope.ServiceProvider
.GetRequiredService<GatewayDbContext>();
var routes = dbContext.ProxyRoutes
.Where(r => r.IsActive)
.Select(r => new RouteConfig
{
RouteId = r.RouteId,
ClusterId = r.ClusterId,
Match = new RouteMatch { Path = r.PathPattern }
})
.ToList();
var clusters = dbContext.ProxyClusters
.Include(c => c.Destinations)
.Select(c => new ClusterConfig
{
ClusterId = c.ClusterId,
Destinations = c.Destinations
.ToDictionary(
d => d.DestinationId,
d => new DestinationConfig { Address = d.Address })
})
.ToList();
return new DatabaseProxyConfig(routes, clusters, _cts.Token);
}
public void Reload()
{
var oldCts = _cts;
_cts = new CancellationTokenSource();
oldCts.Cancel();
}
}
11. YARP vs Other Solutions
| Criteria | YARP | Ocelot | Kong | Nginx |
|---|---|---|---|---|
| Language | C# / .NET | C# / .NET | Lua / Go | C |
| .NET Integration | Native — ASP.NET Core middleware | Good but dated API | Plugin / sidecar | Pure reverse proxy |
| Performance | Very high (~0.2ms overhead) | Moderate | High | Very high |
| Community | Official Microsoft | Community | Enterprise + OSS | Very large |
| Dynamic Config | IProxyConfigProvider | Limited | Admin API | Config file reload |
| gRPC Proxy | Yes | No | Yes | Yes (stream) |
| WebSocket | Yes | Yes | Yes | Yes |
| HTTP/3 | Yes (.NET 10) | No | No | Experimental |
12. Production Checklist
Key Considerations for Production YARP Deployment
- HTTPS Termination: Configure TLS certificates at the gateway, forward internal HTTP to reduce backend overhead
- Connection Pooling: YARP manages HTTP connection pools — adjust
MaxConnectionsPerServerbased on actual load - Timeout Configuration: Set timeouts per cluster — fast services (user lookup) and slow services (report generation) need different timeouts
- Circuit Breaker: Combine with Polly or Microsoft.Extensions.Resilience to prevent cascading failures
- Logging Level: In production, set YARP log level to Warning to avoid log flooding — only enable Information/Debug when troubleshooting
- Header Size Limits: Increase
MaxRequestHeadersTotalSizeif using large JWT tokens (with many claims)
// Production-ready configuration
builder.WebHost.ConfigureKestrel(options =>
{
options.Limits.MaxRequestHeadersTotalSize = 64 * 1024; // 64KB
options.Limits.MaxRequestBodySize = 50 * 1024 * 1024; // 50MB
options.Limits.Http2.MaxStreamsPerConnection = 200;
options.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(2);
});
builder.Services.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"))
.ConfigureHttpClient((context, handler) =>
{
handler.MaxConnectionsPerServer = 100;
handler.EnableMultipleHttp2Connections = true;
handler.SslOptions.RemoteCertificateValidationCallback =
(sender, cert, chain, errors) => true; // Dev only!
});
13. Production Architecture Overview
graph TD
LB[Load Balancer
Cloudflare / ALB] --> G1[YARP Gateway
Instance 1]
LB --> G2[YARP Gateway
Instance 2]
G1 --> AUTH[Auth Middleware
JWT Validation]
G2 --> AUTH
AUTH --> RL[Rate Limiter
Fixed/Sliding/Token]
RL --> TRANSFORM[Request Transforms
Headers, Path Rewrite]
TRANSFORM --> S1[Order Service
3 instances]
TRANSFORM --> S2[Product Service
2 instances]
TRANSFORM --> S3[User Service
2 instances]
G1 --> OTEL[OpenTelemetry
Collector]
G2 --> OTEL
OTEL --> GRAF[Grafana
Dashboard]
style LB fill:#2c3e50,stroke:#fff,color:#fff
style G1 fill:#e94560,stroke:#fff,color:#fff
style G2 fill:#e94560,stroke:#fff,color:#fff
style OTEL fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style GRAF fill:#f8f9fa,stroke:#4CAF50,color:#2c3e50
Conclusion
YARP is more than just a reverse proxy — it's an API Gateway framework that lets you build fully customized gateways within the .NET ecosystem. With native integration into the ASP.NET Core pipeline, YARP lets you leverage everything .NET offers: authentication, rate limiting, logging, DI — without learning additional tools or languages.
For teams building microservices on .NET, YARP is the most natural choice — performance on par with Nginx, more flexible than Ocelot, and backed by Microsoft for the long term.
References
Feature Flags & Progressive Delivery: Safe Release Strategy for Production
Speculation Rules API — Achieve Near-Instant Page Navigations
Disclaimer: The opinions expressed in this blog are solely my own and do not reflect the views or opinions of my employer or any affiliated organizations. The content provided is for informational and educational purposes only and should not be taken as professional advice. While I strive to provide accurate and up-to-date information, I make no warranties or guarantees about the completeness, reliability, or accuracy of the content. Readers are encouraged to verify the information and seek independent advice as needed. I disclaim any liability for decisions or actions taken based on the content of this blog.