Skip to content

YARP Reverse Proxy

Key Points

  • YARP (Yet Another Reverse Proxy) is Microsoft's high-perf reverse proxy library — embedded in your ASP.NET Core app. Written in .NET; HTTP/1, /2, /3; gRPC; WebSockets.
  • Used at huge scale internally at Microsoft. Open-source.
  • Use cases: API gateway, BFF (Backend-for-Frontend), authentication offload, request transformation, A/B routing, sticky sessions, rate limiting at edge.
  • Compared to NGINX/Envoy: YARP runs inside a .NET app — full access to ASP.NET Core middleware, DI, auth, OpenTelemetry.
  • Configuration: appsettings.json declarative, or programmatic with IProxyConfigProvider for dynamic.

Concepts (deep dive)

What is an API Gateway? (generic concept)

Before YARP-specifics: the API Gateway is a generic architectural pattern. Single ingress fronting a fleet of backend services, centralizing cross-cutting concerns so every microservice doesn't re-implement them.

                                   ┌── auth-service
[Client] → [API Gateway] ──┬──→ ┼── orders-service
                          │     ├── catalog-service
                          │     └── payments-service
                cross-cutting concerns applied once at the edge:
                  - authentication / token validation
                  - rate limiting
                  - request/response transformation
                  - caching
                  - request aggregation (fan-out + recombine)
                  - observability (tracing, metrics)
                  - circuit breaking

Why it exists: - Prevents every service from re-implementing auth and rate limiting. - Gives clients one URL even as the service topology changes behind it. - Enables the BFF (Backend-for-Frontend) pattern — a gateway tailored per client (web, mobile, partner). - Decouples client-visible API shape from internal service decomposition.

Common features: routing, authn/authz (token validation, OIDC), rate limiting, request/response transforms, request aggregation (split one client request into N backend calls and recombine), circuit-breaking, response caching, WebSocket / gRPC pass-through, TLS termination.

Tool Style Strengths When to pick
YARP .NET library, in-process Full ASP.NET Core middleware, DI, custom logic in C# .NET shop, programmable gateway
Azure APIM Managed, full lifecycle Developer portal, products, subscriptions, policies Public APIs with monetization, partner mgmt
Azure Front Door Global edge Global routing, WAF, CDN, anycast Multi-region, edge caching, DDoS shield
Ocelot .NET library, declarative Simple JSON config, lighter than YARP Legacy .NET — YARP is the successor
Kong Lua/nginx-based Plugin ecosystem, mature Polyglot shop, plugin requirements
Envoy C++, service-mesh native Highest perf, observability built-in, xDS Service mesh (Istio/Linkerd), polyglot
Traefik Go, container-native Auto-discovery via Docker/K8s labels Container-first, no central config

Where YARP fits: an in-process .NET reverse proxy + gateway library you embed in your own ASP.NET Core app. Trade-off: less ops machinery than APIM/Front Door, but full programmability in C# and the same middleware pipeline as your other apps.

⚠️ Anti-pattern: the gateway as a god object that runs business logic. Keep gateways stateless and edge-only — auth, routing, rate-limit, transforms, observability. Business rules belong in services. A gateway that calls into a database for domain decisions is on its way to becoming a distributed monolith.

Setup

builder.Services.AddReverseProxy()
    .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));

app.MapReverseProxy();

Config:

{
  "ReverseProxy": {
    "Routes": {
      "api-route": {
        "ClusterId": "api-cluster",
        "Match": { "Path": "/api/{**catch-all}" }
      },
      "ui-route": {
        "ClusterId": "ui-cluster",
        "Match": { "Path": "/{**catch-all}" }
      }
    },
    "Clusters": {
      "api-cluster": {
        "Destinations": {
          "api1": { "Address": "https://api-svc-1/" },
          "api2": { "Address": "https://api-svc-2/" }
        },
        "LoadBalancingPolicy": "RoundRobin",
        "HealthCheck": {
          "Active": { "Enabled": true, "Interval": "00:00:10", "Path": "/health" }
        }
      },
      "ui-cluster": {
        "Destinations": { "ui": { "Address": "https://frontend/" } }
      }
    }
  }
}

Concepts

  • Route — match incoming request (path, host, method, header).
  • Cluster — set of destinations (target servers).
  • Destination — actual upstream URL.
  • Load balancing policies — Random, RoundRobin, LeastRequests, PowerOfTwoChoices.

Use cases

API Gateway

client → YARP → /users → users-service
                /orders → orders-service
                /payments → payments-service

One entry point; routes by path/host. Add cross-cutting (auth, rate limit, logging) at the gateway.

BFF (Backend-for-Frontend)

SPA → YARP (BFF) → many backend APIs
       ├── auth (cookies in; bearer out)
       └── aggregation

The BFF holds tokens server-side, talks to backends with bearer, returns to SPA via cookies. Critical for security (JWT Validation Pitfalls).

Authentication offload

app.UseAuthentication();
app.MapReverseProxy().RequireAuthorization();   // only authed pass to upstream

YARP authenticates; upstream doesn't need to. Or: forward auth claims as headers.

Request transformation

"Transforms": [
  { "PathPrefix": "/v2" },
  { "RequestHeader": "X-Api-Key", "Set": "secret" },
  { "ResponseHeader": "X-Powered-By", "Remove": "" }
]

Path rewrite, header add/remove, query manipulation. Common for backend API normalization.

Health checks

Active: YARP polls /health periodically; marks destinations unhealthy.

"HealthCheck": {
  "Active": {
    "Enabled": true,
    "Interval": "00:00:10",
    "Timeout": "00:00:05",
    "Policy": "ConsecutiveFailures",
    "Path": "/health"
  }
}

Passive: marks unhealthy on consecutive proxied request failures. No extra traffic.

Sticky sessions

"SessionAffinity": {
  "Enabled": true,
  "Policy": "Cookie",
  "AffinityKeyName": ".YarpAffinity"
}

Same client → same destination. Useful for stateful backends (SignalR before backplane, in-process state).

Dynamic configuration

public class MyConfigProvider : IProxyConfigProvider { /* ... */ }

builder.Services.AddSingleton<IProxyConfigProvider, MyConfigProvider>();
builder.Services.AddReverseProxy();

Reload routes/clusters at runtime — based on Consul, k8s API, custom logic.

gRPC support

"Routes": {
  "grpc-route": {
    "ClusterId": "grpc-cluster",
    "Match": { "Path": "/grpc/{**catch-all}" }
  }
}
"Clusters": {
  "grpc-cluster": {
    "HttpRequest": { "Version": "2", "VersionPolicy": "RequestVersionExact" },
    "Destinations": { "g1": { "Address": "https://grpc-svc/" } }
  }
}

YARP routes gRPC seamlessly.

WebSockets

Native support. Just route via path — YARP detects the upgrade and forwards.

Rate limiting

app.UseRateLimiter();
app.MapReverseProxy().RequireRateLimiting("api");

Edge-level rate limiting protects all backends.

OpenTelemetry

YARP emits traces/metrics. Integrates with AddOpenTelemetry.

Compared to other proxies

Tool Style Strengths
YARP .NET library Full ASP.NET Core integration
NGINX C; declarative Highest perf; mature; not .NET
Envoy C++; rich features Service mesh; heavy
Traefik Go; service discovery Auto-config; container-native
HAProxy C; load balancing TCP + HTTP; battle-tested
Azure Application Gateway Managed Cloud integration

YARP's edge: when you want a reverse proxy inside a .NET app — full ASP.NET pipeline, DI, middleware. NGINX/Envoy/Traefik are external processes.

When YARP

  • Need ASP.NET Core middleware in front of upstreams (auth, custom logic).
  • BFF for SPA.
  • Custom routing logic (in C#) hard in declarative tools.
  • .NET-only shop; ops bias toward .NET.

When NOT YARP

  • Pure load balancing at huge scale → NGINX or Application Gateway.
  • Service mesh — Envoy / Linkerd.
  • Service discovery + routing → Traefik / Consul.

Performance

YARP is fast — comparable to NGINX in benchmarks. Microsoft uses it at AWS-scale (sort of) for internal services.


Code: correct vs wrong

❌ Wrong: rolling your own with HttpClient + manual stream

app.MapGet("/{*url}", async (HttpContext ctx, IHttpClientFactory hf) =>
{
    var http = hf.CreateClient();
    var resp = await http.GetAsync($"https://upstream/{url}");
    /* manual stream copy, header forwarding... 200 lines and bugs */
});

✅ Correct: YARP

app.MapReverseProxy();

❌ Wrong: forwarding without health checks

// Backend down → YARP keeps sending → user errors

✅ Correct: active health check

"HealthCheck": { "Active": { "Enabled": true, "Path": "/health" } }

Design patterns for this topic

Pattern 1 — "API Gateway with YARP"

  • Intent: unified entry; cross-cutting at edge.

Pattern 2 — "BFF for SPA"

  • Intent: server-side token holder; cookie session for browser.

Pattern 3 — "Active + Passive health checks"

  • Intent: detect failures fast; don't spam unhealthy.

Pattern 4 — "Dynamic config via IProxyConfigProvider"

  • Intent: runtime route updates.

Pattern 5 — "Transforms for backend normalization"

  • Intent: rewrite paths/headers without backend changes.

Pros & cons / trade-offs

Aspect Pros Cons
YARP .NET-native; flexible Yet another moving part
NGINX Mature; fast External process
Envoy Service-mesh ready Complex
Application Gateway Managed Cloud-coupled

When to use / when to avoid

  • Use YARP for BFF, API gateway in .NET shop.
  • Use NGINX/Envoy for pure load balancing or service mesh.
  • Avoid rolling your own.

Interview Q&A

Q1. What is YARP? Microsoft's reverse proxy library; embeds in ASP.NET Core; HTTP/½/3, gRPC, WebSockets.

Q2. YARP vs NGINX? YARP runs in your .NET app; full middleware access. NGINX is external; faster baseline; mature.

Q3. BFF pattern with YARP? SPAYARP (BFF) — cookie session for browser; bearer token to backends; YARP holds the token.

Q4. Active vs passive health check? Active: poll endpoint. Passive: track proxied request failures.

Q5. Sticky sessions? Same client → same destination via cookie/header. Useful for stateful backends.

Q6. Dynamic configuration? IProxyConfigProvider lets you update routes/clusters at runtime.

Q7. Transforms? Path rewrite, header add/remove, query mods at the proxy.

Q8. WebSockets through YARP? Native; auto-detects upgrade; forwards.

Q9. gRPC? Configure cluster with HttpRequest.Version = "2". Routes gRPC.

Q10. Why YARP over rolling your own? Stream copying, header handling, websocket upgrades, gRPC trailers — many bugs without YARP.

Q11. Authentication offload? YARP authenticates; backend trusts forwarded headers/claims.

Q12. Rate limiting at YARP? Use ASP.NET Core rate limiter middleware before MapReverseProxy().


Gotchas / common mistakes

  • ⚠️ No health checks — proxying to dead destinations.
  • ⚠️ Rolling your own — buggy.
  • ⚠️ Wrong HTTP version for gRPC clusters.
  • ⚠️ No auth at gateway — every backend re-auths.
  • ⚠️ Sticky sessions on stateless backends — uneven load.

Further reading