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.jsondeclarative, or programmatic withIProxyConfigProviderfor 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
One entry point; routes by path/host. Add cross-cutting (auth, rate limit, logging) at the gateway.
BFF (Backend-for-Frontend)
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
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
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
❌ Wrong: forwarding without health checks
✅ Correct: active health check
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? SPA → YARP (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.