YARP (Yet Another Reverse Proxy) :👈 👉:Build taxi-booking application

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

Internet Azure Front Door/ Cloudflare Application Gateway / WAF YARP Gateway User Service Product Service Order Service Payment Service

πŸ“ 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<ForwardedHeadersOptions>(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<CorrelationIdMiddleware>();

πŸ“Š 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.

Back to Index
YARP (Yet Another Reverse Proxy) :👈 👉:Build taxi-booking application