| YARP (Yet Another Reverse Proxy) :👈 | 👉:Tenant ID |
Production Ready YARP Setup |
π Production-Ready YARP Setup
A production-ready YARP setup is usually much more than MapReverseProxy() and a couple of routes. In enterprise .NET environments, YARP often acts as an API Gateway responsible for:
- β Routing
- β Authentication
- β Authorization
- β Rate limiting
- β Health checks
- β Service discovery
- β Correlation IDs
- β Distributed tracing
- β Logging
- β Resilience
- β Load balancing
ποΈ Common Production Architecture
π Solution Structure
Gateway β βββ Program.cs βββ appsettings.json βββ Middleware β βββ CorrelationIdMiddleware.cs β βββ Extensions β βββ ServiceCollectionExtensions.cs β βββ Controllers
π¦ NuGet Packages
dotnet add package Yarp.ReverseProxy
dotnet add package Microsoft.AspNetCore.RateLimiting
dotnet add package AspNetCore.HealthChecks.UI.Client
dotnet add package OpenTelemetry.Extensions.Hosting
dotnet add package OpenTelemetry.Exporter.Console
βοΈ Production appsettings.json
This example has:
- β User service
- β Product service
- β Multiple replicas
- β Load balancing
{
"ReverseProxy": {
"Routes": {
"users-route": {
"ClusterId": "users-cluster",
"Match": {
"Path": "/api/users/{**catch-all}"
}
},
"products-route": {
"ClusterId": "products-cluster",
"Match": {
"Path": "/api/products/{**catch-all}"
}
}
},
"Clusters": {
"users-cluster": {
"LoadBalancingPolicy": "RoundRobin",
"Destinations": {
"api1": {
"Address": "https://users-api-1/"
},
"api2": {
"Address": "https://users-api-2/"
}
}
},
"products-cluster": {
"LoadBalancingPolicy": "RoundRobin",
"Destinations": {
"api1": {
"Address": "https://products-api-1/"
},
"api2": {
"Address": "https://products-api-2/"
}
}
}
}
}
}
π οΈ Program.cs
A production startup configuration.
using System.Threading.RateLimiting;
using Microsoft.AspNetCore.HttpOverrides;
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddReverseProxy()
.LoadFromConfig(
builder.Configuration.GetSection("ReverseProxy"));
builder.Services.AddHealthChecks();
builder.Services.AddHttpContextAccessor();
builder.Services.AddRateLimiter(options =>
{
options.AddFixedWindowLimiter(
"api",
limiterOptions =>
{
limiterOptions.PermitLimit = 100;
limiterOptions.Window = TimeSpan.FromMinutes(1);
});
});
builder.Services.Configure(options =>
{
options.ForwardedHeaders =
ForwardedHeaders.XForwardedFor |
ForwardedHeaders.XForwardedProto;
});
var app = builder.Build();
app.UseForwardedHeaders();
app.UseRateLimiter();
app.UseHttpsRedirection();
app.MapHealthChecks("/health");
app.MapReverseProxy();
app.Run();
π Authentication at Gateway
One of the most common production patterns.
Instead of every microservice validating JWT tokens:
Client | JWT | YARP Gateway | Validated | Microservices
Configure JWT:
builder.Services
.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority = "https://identity.company.com";
options.Audience = "gateway-api";
});
builder.Services.AddAuthorization();
Enable:
app.UseAuthentication(); app.UseAuthorization();
Routes:
{
"Routes": {
"users-route": {
"ClusterId": "users-cluster",
"AuthorizationPolicy": "default",
"Match": {
"Path": "/api/users/{**catch-all}"
}
}
}
}
π Correlation ID
Critical for troubleshooting across services.
Request:
RequestId=ABC123
Must follow every API call.
Middleware:
public class CorrelationIdMiddleware
{
private readonly RequestDelegate _next;
public CorrelationIdMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
var correlationId =
context.Request.Headers["X-Correlation-ID"]
.FirstOrDefault()
?? Guid.NewGuid().ToString();
context.Response.Headers["X-Correlation-ID"] =
correlationId;
await _next(context);
}
}
Register:
app.UseMiddleware();
π Logging
Never log only:
logger.LogInformation("Request received");
Instead:
logger.LogInformation( "Path:{Path} Correlation:{CorrelationId}", context.Request.Path, correlationId);
Good logs answer:
- π€ Who called?
- π When?
- π What endpoint?
- π§ Which service?
- π Correlation Id?
- β±οΈ Execution time?
π‘ OpenTelemetry
Modern production systems use distributed tracing.
builder.Services
.AddOpenTelemetry()
.WithTracing(tracing =>
{
tracing.AddAspNetCoreInstrumentation();
tracing.AddHttpClientInstrumentation();
tracing.AddConsoleExporter();
});
Flow:
Gateway | | TraceId | User Service | Order Service | Payment Service
β Single trace across all services.
π‘οΈ Retry & Resilience
Backend services occasionally fail.
Use HttpClient resilience.
builder.Services .AddHttpClient("proxy") .AddStandardResilienceHandler();
Handles:
- β Retries
- β Timeout
- β Circuit breaker
β€οΈ Health Checks
Every service should expose:
/health
Example:
app.MapHealthChecks("/health");
Response:
{ "status": "Healthy" }
β Load balancers can remove unhealthy instances automatically.
π¦ Rate Limiting
Prevent abuse.
builder.Services.AddRateLimiter(options =>
{
options.AddFixedWindowLimiter(
"gateway",
config =>
{
config.PermitLimit = 100;
config.Window = TimeSpan.FromMinutes(1);
});
});
Example:
100 requests/minute
After that:
429 Too Many Requests
π Header Hardening
Add security headers.
app.Use(async (context, next) =>
{
context.Response.Headers.Append(
"X-Content-Type-Options",
"nosniff");
context.Response.Headers.Append(
"X-Frame-Options",
"DENY");
context.Response.Headers.Append(
"Referrer-Policy",
"strict-origin");
await next();
});
βΈοΈ Kubernetes Production Setup
Instead of hardcoding servers:
"Address": "https://10.1.5.23:5000"
Use service DNS:
"Address": "http://user-service/"
Architecture:
YARP | +--> user-service +--> order-service +--> payment-service
β Kubernetes resolves service addresses automatically.
β What Most Developers Get Wrong
1. Storing Sessions In Memory
Bad:
Gateway | App1 App2
with:
AddSession()
using in-memory store. User may hit different servers.
Use:
Redis SQL Distributed Cache
2. No Forwarded Headers
Wrong client IP.
app.UseForwardedHeaders();
is mandatory behind proxies.
3. Gateway Contains Business Logic
Bad:
YARP | + Calculate Taxes + Create Orders
Gateway should focus on:
- β Routing
- β Security
- β Validation
- β Observability
Business logic belongs to services.
4. No Timeouts
A slow downstream service can consume all gateway threads.
Configure:
{ "HttpRequest": { "ActivityTimeout": "00:00:30" } }
β Enterprise-Grade Setup Checklist
- β HTTPS everywhere
- β JWT authentication
- β Authorization policies
- β Health checks
- β Rate limiting
- β Correlation IDs
- β Structured logging (Serilog)
- β OpenTelemetry tracing
- β Retry policies
- β Distributed cache
- β Load balancing
- β Security headers
- β Kubernetes service discovery
- β Centralized configuration
- β Monitoring (Grafana/AppInsights)
- β WAF in front of gateway
This gives you centralized security, routing, observability, and scalability while keeping individual microservices simple and focused on business logic.
| YARP (Yet Another Reverse Proxy) :👈 | 👉:Tenant ID |