YARP — Building API Gateway for Microservices on .NET 10

Posted on: 4/24/2026 8:11:50 PM

50M+ NuGet downloads for YARP
~0.2ms Overhead per request
HTTP/3 Latest protocol support
100% ASP.NET Core integration

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
API Gateway architecture with YARP — single entry point for microservices

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.

PolicyDescriptionBest For
RoundRobinDistributes evenly across instancesIdentically configured instances
LeastRequestsRoutes to instance with fewest active requestsRequests with varying processing times
RandomRandom selectionTesting, development
PowerOfTwoChoicesPicks better of two random instancesBalance between random and optimal
FirstAlphabeticalAlways routes to first instanceActive-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
Health Check flow — automatic instance removal and recovery

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
YARP Transform Pipeline — processing requests and responses

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
BFF Pattern — specialized gateways for each client type

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

CriteriaYARPOcelotKongNginx
LanguageC# / .NETC# / .NETLua / GoC
.NET IntegrationNative — ASP.NET Core middlewareGood but dated APIPlugin / sidecarPure reverse proxy
PerformanceVery high (~0.2ms overhead)ModerateHighVery high
CommunityOfficial MicrosoftCommunityEnterprise + OSSVery large
Dynamic ConfigIProxyConfigProviderLimitedAdmin APIConfig file reload
gRPC ProxyYesNoYesYes (stream)
WebSocketYesYesYesYes
HTTP/3Yes (.NET 10)NoNoExperimental

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 MaxConnectionsPerServer based 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 MaxRequestHeadersTotalSize if 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
YARP Gateway in production — high availability with observability
2020
Microsoft announces YARP as preview, used internally for Azure Application Gateway
2021
YARP 1.0 GA — .NET 6 support, HTTP/2, load balancing, health checks
2022–2023
YARP 2.x — gRPC proxy, WebSocket improvements, dynamic configuration API
2024–2025
Native integration with .NET 8/9 Rate Limiting, OpenTelemetry, experimental HTTP/3
2026
YARP on .NET 10 — stable HTTP/3, Native AOT support, Microsoft.Extensions.Resilience integration

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