YARP Setup for Multiple Sites


A brief tutorial on how to setup YARP (Yet Another Reverse Proxy) to work with multiple domains.

YARP Setup for Multiple Sites

YARP (Yet Another Reverse Proxy) is Microsoft's high performance reverse proxy that provides a cross platform solution for reverse proxies (and one that uniquely is delivered via a NuGet package so that you as a programmer have fine tuned control of its pipeline). Currently as of the writing of this post YARP doesn't have a standalone installer, you add the NuGet in and compile an EXE that is the proxy (that proxy app will likely be less 30 lines of code>).

In this post I'll share both an example config that works to route two different domains to the dotnet executables running those sites as well as the bare minimum C# to make the reverse proxy work. In the below appsettings I have two sites setup, the first is for dev.mydomain.com and the second is mydomain.com and www.mydomain.com. As you can see, cluster1 in the Routes second maps to the local executable and port that runs that site in the Clusters section. In this scenario, I have the proxy as well as both sites running on the same server. However, that isn't a requirement. The sites that YARP routes to can live on different servers. This is neat because you can create a custom and simple load balancers in an environment where those don't exist (e.g. if you're self hosting). All of the domains in the appsettings.json have been set to point to the IP address of the web server via the domain registrar.

I should note that this example is only using http. If you don't have a certificate and don't want to use Let's Encrypt SSL you can consider using Cloudflare's free offerings. The free offering encrypts the end user to cloudflare, but not from cloudflare to your web server (this is better than nothing but still isn't acceptable for many applications).

appsettings.json

{
  "Urls": "http://*:80",
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "ReverseProxy": {
    "Routes": {
      "route1": {
        "ClusterId": "cluster1",
        "Match": {
          "Path": "{**catch-all}",
          "Hosts": [ "dev.mydomain.com" ]
        }
      },
      "route2": {
        "ClusterId": "cluster2",
        "Match": {
          "Path": "{**catch-all}",
          "Hosts": [ "mydomain.com", "www.mydomain.com" ]
        }
      }
    },
    "Clusters": {
      "cluster1": {
        "Destinations": {
          "destination1": {
            "Address": "http://localhost:5000/"
          }
        }
      },
      "cluster2": {
        "Destinations": {
          "destination1": {
            "Address": "http://localhost:6000/"
          }
        }
      }
    }
  }
}

Now, the C#/dotnet required to create a YARP executable. First, create a new .NET 6 console application and add a NuGet reference to Yarp.ReverseProxy. Next, copy the above appsettings.json into your project and update the domains to your domains (and the local servers to your local servers). Of special note, builderContext.AddOriginalHost(useOriginal: true); is an important line if you're forwarding multiple domains to a site and expect that site to know which domain was requested. I've recently started to learn / use Piranha CMS which has the capability to deliver content for multiple domains in one instance but in order for that to work the host and headers have to be forwarded along to it.

Program.cs for YARP

using Yarp.ReverseProxy.Transforms;

Console.WriteLine($"[{DateTime.Now.ToString()}] Reverse Proxy Startup");

var builder = WebApplication.CreateBuilder(args);

_ = builder.Services.AddReverseProxy()
                    .AddTransforms(builderContext =>
                    {
                        builderContext.CopyRequestHeaders = true;
                        builderContext.AddOriginalHost(useOriginal: true);
                        builderContext.UseDefaultForwarders = true;
                    })
                    .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}

app.MapReverseProxy();
app.Run();