Configure ASP.NET Core to work with proxy servers and load balancers
Note
This isn't the latest version of this article. For the current release, see the .NET 8 version of this article.
Warning
This version of ASP.NET Core is no longer supported. For more information, see .NET and .NET Core Support Policy. For the current release, see the .NET 8 version of this article.
Important
This information relates to a pre-release product that may be substantially modified before it's commercially released. Microsoft makes no warranties, express or implied, with respect to the information provided here.
For the current release, see the .NET 8 version of this article.
By Chris Ross
In the recommended configuration for ASP.NET Core, the app is hosted using ASP.NET Core Module (ANCM) for IIS, Nginx, or Apache. Proxy servers, load balancers, and other network appliances often obscure information about the request before it reaches the app:
- When HTTPS requests are proxied over HTTP, the original scheme (HTTPS) is lost and must be forwarded in a header.
- Because an app receives a request from the proxy and not its true source on the Internet or corporate network, the originating client IP address must also be forwarded in a header.
This information may be important in request processing, for example in redirects, authentication, link generation, policy evaluation, and client geolocation.
Apps intended to run on web farm should read Host ASP.NET Core in a web farm.
Forwarded headers
By convention, proxies forward information in HTTP headers.
Header | Description |
---|---|
X-Forwarded-For (XFF) |
Holds information about the client that initiated the request and subsequent proxies in a chain of proxies. This parameter may contain IP addresses and, optionally, port numbers. In a chain of proxy servers, the first parameter indicates the client where the request was first made. Subsequent proxy identifiers follow. The last proxy in the chain isn't in the list of parameters. The last proxy's IP address, and optionally a port number, are available as the remote IP address at the transport layer. |
X-Forwarded-Proto (XFP) |
The value of the originating scheme, HTTP or HTTPS. The value may also be a list of schemes if the request has traversed multiple proxies. |
X-Forwarded-Host (XFH) |
The original value of the Host header field. Usually, proxies don't modify the Host header. See Microsoft Security Advisory CVE-2018-0787 for information on an elevation-of-privileges vulnerability that affects systems where the proxy doesn't validate or restrict Host headers to known good values. |
X-Forwarded-Prefix |
The original base path requested by the client. This header can be useful for applications to correctly generate URLs, redirects, or links back to the client. |
The Forwarded Headers Middleware, ForwardedHeadersMiddleware, reads these headers and fills in the associated fields on HttpContext.
The middleware updates:
HttpContext.Connection.RemoteIpAddress
: Set using theX-Forwarded-For
header value. Additional settings influence how the middleware setsRemoteIpAddress
. For details, see the Forwarded Headers Middleware options. The consumed values are removed fromX-Forwarded-For
, and the old value ofHttpContext.Connection.RemoteIpAddress
is persisted inX-Original-For
. Note: This process may be repeated several times if there are multiple values inX-Forwarded-For/Proto/Host/Prefix
, resulting in several values moved toX-Original-*
, including the originalRemoteIpAddress/Host/Scheme/PathBase
.HttpContext.Request.Scheme
: Set using theX-Forwarded-Proto
header value. The consumed value is removed fromX-Forwarded-Proto
, and the old value ofHttpContext.Request.Scheme
is persisted inX-Original-Proto
.HttpContext.Request.Host
: Set using theX-Forwarded-Host
header value. The consumed value is removed fromX-Forwarded-Host
, and the old value ofHttpContext.Request.Host
is persisted inX-Original-Host
.HttpContext.Request.PathBase
: Set using theX-Forwarded-Prefix
header value. The consumed value is removed fromX-Forwarded-Prefix
, and the old value ofHttpContext.Request.PathBase
is persisted inX-Original-Prefix
.
For more information on the preceding, see this GitHub issue.
Forwarded Headers Middleware default settings can be configured. For the default settings:
- There is only one proxy between the app and the source of the requests.
- Only loopback addresses are configured for known proxies and known networks.
- The forwarded headers are named
X-Forwarded-For
,X-Forwarded-Proto
,X-Forwarded-Host
andX-Forwarded-Prefix
. - The
ForwardedHeaders
value isForwardedHeaders.None
, the desired forwarders must be set here to enable the middleware.
Not all network appliances add the X-Forwarded-For
and X-Forwarded-Proto
headers without additional configuration. Consult your appliance manufacturer's guidance if proxied requests don't contain these headers when they reach the app. If the appliance uses different header names than X-Forwarded-For
and X-Forwarded-Proto
, set the ForwardedForHeaderName and ForwardedProtoHeaderName options to match the header names used by the appliance. For more information, see Forwarded Headers Middleware options and Configuration for a proxy that uses different header names.
IIS/IIS Express and ASP.NET Core Module
Forwarded Headers Middleware is enabled by default by IIS Integration Middleware when the app is hosted out-of-process behind IIS and the ASP.NET Core Module (ANCM) for IIS. Forwarded Headers Middleware is activated to run first in the middleware pipeline with a restricted configuration specific to the ASP.NET Core Module. The restricted configuration is due to trust concerns with forwarded headers, for example, IP spoofing. The middleware is configured to forward the X-Forwarded-For
and X-Forwarded-Proto
headers and is restricted to a single localhost proxy. If additional configuration is required, see the Forwarded Headers Middleware options.
Other proxy server and load balancer scenarios
Outside of using IIS Integration when hosting out-of-process, Forwarded Headers Middleware isn't enabled by default. Forwarded Headers Middleware must be enabled for an app to process forwarded headers with UseForwardedHeaders. After enabling the middleware if no ForwardedHeadersOptions are specified to the middleware, the default ForwardedHeadersOptions.ForwardedHeaders are ForwardedHeaders.None.
Configure the middleware with ForwardedHeadersOptions to forward the X-Forwarded-For
and X-Forwarded-Proto
headers.
Forwarded Headers Middleware order
Forwarded Headers Middleware should run before other middleware. This ordering ensures that the middleware relying on forwarded headers information can consume the header values for processing. Forwarded Headers Middleware can run after diagnostics and error handling, but it must be run before calling UseHsts:
using Microsoft.AspNetCore.HttpOverrides;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders =
ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
});
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseForwardedHeaders();
app.UseHsts();
}
else
{
app.UseDeveloperExceptionPage();
app.UseForwardedHeaders();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
Alternatively, call UseForwardedHeaders
before diagnostics:
using Microsoft.AspNetCore.HttpOverrides;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders =
ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
});
var app = builder.Build();
app.UseForwardedHeaders();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
Note
If no ForwardedHeadersOptions are specified or applied directly to the extension method with UseForwardedHeaders, the default headers to forward are ForwardedHeaders.None. The ForwardedHeaders property must be configured with the headers to forward.
Nginx configuration
To forward the X-Forwarded-For
and X-Forwarded-Proto
headers, see Host ASP.NET Core on Linux with Nginx. For more information, see NGINX: Using the Forwarded header.
Apache configuration
X-Forwarded-For
is added automatically. For more information, see Apache Module mod_proxy: Reverse Proxy Request Headers.
Forwarded Headers Middleware options
ForwardedHeadersOptions control the behavior of the Forwarded Headers Middleware. The following example changes the default values:
- Limits the number of entries in the forwarded headers to
2
. - Adds a known proxy address of
127.0.10.1
. - Changes the forwarded header name from the default
X-Forwarded-For
toX-Forwarded-For-My-Custom-Header-Name
.
using System.Net;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardLimit = 2;
options.KnownProxies.Add(IPAddress.Parse("127.0.10.1"));
options.ForwardedForHeaderName = "X-Forwarded-For-My-Custom-Header-Name";
});
var app = builder.Build();
app.UseForwardedHeaders();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
Option | Description |
---|---|
AllowedHosts | Restricts hosts by the X-Forwarded-Host header to the values provided.
IList<string> . |
ForwardedForHeaderName | Use the header specified by this property instead of the one specified by ForwardedHeadersDefaults.XForwardedForHeaderName. This option is used when the proxy/forwarder doesn't use the X-Forwarded-For header but uses some other header to forward the information.The default is X-Forwarded-For . |
ForwardedHeaders | Identifies which forwarders should be processed. See the ForwardedHeaders Enum for the list of fields that apply. Typical values assigned to this property are ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto .The default value is ForwardedHeaders.None. |
ForwardedHostHeaderName | Use the header specified by this property instead of the one specified by ForwardedHeadersDefaults.XForwardedHostHeaderName. This option is used when the proxy/forwarder doesn't use the X-Forwarded-Host header but uses some other header to forward the information.The default is X-Forwarded-Host . |
ForwardedProtoHeaderName | Use the header specified by this property instead of the one specified by ForwardedHeadersDefaults.XForwardedProtoHeaderName. This option is used when the proxy/forwarder doesn't use the X-Forwarded-Proto header but uses some other header to forward the information.The default is X-Forwarded-Proto . |
ForwardLimit | Limits the number of entries in the headers that are processed. Set to null to disable the limit, but this should only be done if KnownProxies or KnownNetworks are configured. Setting a non-null value is a precaution (but not a guarantee) to protect against misconfigured proxies and malicious requests arriving from side-channels on the network.Forwarded Headers Middleware processes headers in reverse order from right to left. If the default value ( 1 ) is used, only the rightmost value from the headers is processed unless the value of ForwardLimit is increased.The default is 1 . |
KnownNetworks | Address ranges of known networks to accept forwarded headers from. Provide IP ranges using Classless Interdomain Routing (CIDR) notation. If the server is using dual-mode sockets, IPv4 addresses are supplied in an IPv6 format (for example, 10.0.0.1 in IPv4 represented in IPv6 as ::ffff:10.0.0.1 ). See IPAddress.MapToIPv6. Determine if this format is required by looking at the HttpContext.Connection.RemoteIpAddress.The default is an IList <IPNetwork> containing a single entry for new IPNetwork(IPAddress.Loopback, 8) . |
KnownProxies | Addresses of known proxies to accept forwarded headers from. Use KnownProxies to specify exact IP address matches.If the server is using dual-mode sockets, IPv4 addresses are supplied in an IPv6 format (for example, 10.0.0.1 in IPv4 represented in IPv6 as ::ffff:10.0.0.1 ). See IPAddress.MapToIPv6. Determine if this format is required by looking at the HttpContext.Connection.RemoteIpAddress.The default is an IList <IPAddress> containing a single entry for IPAddress.IPv6Loopback . |
OriginalForHeaderName | Use the header specified by this property instead of the one specified by ForwardedHeadersDefaults.XOriginalForHeaderName. The default is X-Original-For . |
OriginalHostHeaderName | Use the header specified by this property instead of the one specified by ForwardedHeadersDefaults.XOriginalHostHeaderName. The default is X-Original-Host . |
OriginalProtoHeaderName | Use the header specified by this property instead of the one specified by ForwardedHeadersDefaults.XOriginalProtoHeaderName. The default is X-Original-Proto . |
RequireHeaderSymmetry | Require the number of header values to be in sync between the ForwardedHeadersOptions.ForwardedHeaders being processed. The default in ASP.NET Core 1.x is true . The default in ASP.NET Core 2.0 or later is false . |
Scenarios and use cases
When it isn't possible to add forwarded headers and all requests are secure
In some cases, it might not be possible to add forwarded headers to the requests proxied to the app. If the proxy is enforcing that all public external requests are HTTPS, the scheme can be manually set before using any type of middleware:
using Microsoft.AspNetCore.HttpOverrides;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders =
ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
});
var app = builder.Build();
app.Use((context, next) =>
{
context.Request.Scheme = "https";
return next(context);
});
app.UseForwardedHeaders();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
This code can be disabled with an environment variable or other configuration setting in a development or staging environment:
using Microsoft.AspNetCore.HttpOverrides;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders =
ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
});
var app = builder.Build();
if (!app.Environment.IsProduction())
{
app.Use((context, next) =>
{
context.Request.Scheme = "https";
return next(context);
});
}
app.UseForwardedHeaders();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
Work with path base and proxies that change the request path
Some proxies pass the path intact but with an app base path that should be removed so that routing works properly. UsePathBaseExtensions.UsePathBase middleware splits the path into HttpRequest.Path and the app base path into HttpRequest.PathBase.
If /foo
is the app base path for a proxy path passed as /foo/api/1
, the middleware sets Request.PathBase
to /foo
and Request.Path
to /api/1
with the following command:
app.UsePathBase("/foo");
// ...
app.UseRouting();
Note
When using WebApplication (see Migrate from ASP.NET Core 5.0 to 6.0), app.UseRouting
must be called after UsePathBase
so that the routing middleware can observe the modified path before matching routes. Otherwise, routes are matched before the path is rewritten by UsePathBase
as described in the Middleware Ordering and Routing articles.
The original path and path base are reapplied when the middleware is called again in reverse. For more information on middleware order processing, see ASP.NET Core Middleware.
If the proxy trims the path (for example, forwarding /foo/api/1
to /api/1
), fix redirects and links by setting the request's PathBase property:
app.Use((context, next) =>
{
context.Request.PathBase = new PathString("/foo");
return next(context);
});
If the proxy is adding path data, discard part of the path to fix redirects and links by using StartsWithSegments and assigning to the Path property:
app.Use((context, next) =>
{
if (context.Request.Path.StartsWithSegments("/foo", out var remainder))
{
context.Request.Path = remainder;
}
return next(context);
});
Configuration for a proxy that uses different header names
If the proxy doesn't use headers named X-Forwarded-For
and X-Forwarded-Proto
to forward the proxy address/port and originating scheme information, set the ForwardedForHeaderName and ForwardedProtoHeaderName options to match the header names used by the proxy:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedForHeaderName = "HeaderNamUsedByProxy_X-Forwarded-For_Header";
options.ForwardedProtoHeaderName = "HeaderNamUsedByProxy_X-Forwarded-Proto_Header";
});
var app = builder.Build();
app.UseForwardedHeaders();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
Forward the scheme for Linux and non-IIS reverse proxies
Apps that call UseHttpsRedirection and UseHsts put a site into an infinite loop if deployed to an Azure Linux App Service, Azure Linux virtual machine (VM), or behind any other reverse proxy besides IIS. TLS is terminated by the reverse proxy, and Kestrel isn't made aware of the correct request scheme. OAuth and OIDC also fail in this configuration because they generate incorrect redirects. UseIISIntegration adds and configures Forwarded Headers Middleware when running behind IIS, but there's no matching automatic configuration for Linux (Apache or Nginx integration).
To forward the scheme from the proxy in non-IIS scenarios, enable the Forwarded Headers Middleware by setting ASPNETCORE_FORWARDEDHEADERS_ENABLED
to true
. Warning: This flag uses settings designed for cloud environments and doesn't enable features such as the KnownProxies option
to restrict which IPs forwarders are accepted from.
Certificate forwarding
Azure
To configure Azure App Service for certificate forwarding, see Configure TLS mutual authentication for Azure App Service. The following guidance pertains to configuring the ASP.NET Core app.
- Configure Certificate Forwarding Middleware to specify the header name that Azure uses. Add the following code to configure the header from which the middleware builds a certificate.
- Call UseCertificateForwarding before the call to UseAuthentication.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddCertificateForwarding(options =>
options.CertificateHeader = "X-ARR-ClientCert");
var app = builder.Build();
app.UseCertificateForwarding();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthorization();
app.UseAuthentication();
app.MapRazorPages();
app.Run();
Other web proxies
If a proxy is used that isn't IIS or Azure App Service's Application Request Routing (ARR), configure the proxy to forward the certificate that it received in an HTTP header.
- Configure Certificate Forwarding Middleware to specify the header name. Add the following code to configure the header from which the middleware builds a certificate.
- Call UseCertificateForwarding before the call to UseAuthentication.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddCertificateForwarding(options =>
options.CertificateHeader = "YOUR_CERTIFICATE_HEADER_NAME");
var app = builder.Build();
app.UseCertificateForwarding();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthorization();
app.UseAuthentication();
app.MapRazorPages();
app.Run();
If the proxy isn't base64-encoding the certificate, as is the case with Nginx, set the HeaderConverter
option. Consider the following example:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddCertificateForwarding(options =>
{
options.CertificateHeader = "YOUR_CUSTOM_HEADER_NAME";
options.HeaderConverter = (headerValue) =>
{
// Conversion logic to create an X509Certificate2.
var clientCertificate = ConversionLogic.CreateAnX509Certificate2();
return clientCertificate;
};
});
var app = builder.Build();
app.UseCertificateForwarding();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthorization();
app.UseAuthentication();
app.MapRazorPages();
app.Run();
Troubleshoot
When headers aren't forwarded as expected, enable debug
level logging and HTTP request logging. UseHttpLogging must be called after UseForwardedHeaders:
using Microsoft.AspNetCore.HttpLogging;
using Microsoft.AspNetCore.HttpOverrides;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddHttpLogging(options =>
{
options.LoggingFields = HttpLoggingFields.RequestPropertiesAndHeaders;
});
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders =
ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
});
var app = builder.Build();
app.UseForwardedHeaders();
app.UseHttpLogging();
app.Use(async (context, next) =>
{
// Connection: RemoteIp
app.Logger.LogInformation("Request RemoteIp: {RemoteIpAddress}",
context.Connection.RemoteIpAddress);
await next(context);
});
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
If there are multiple values in a given header, Forwarded Headers Middleware processes headers in reverse order from right to left. The default ForwardLimit
is 1
(one), so only the rightmost value from the headers is processed unless the value of ForwardLimit
is increased.
The request's original remote IP must match an entry in the KnownProxies or KnownNetworks lists before forwarded headers are processed. This limits header spoofing by not accepting forwarders from untrusted proxies. When an unknown proxy is detected, logging indicates the address of the proxy:
September 20th 2018, 15:49:44.168 Unknown proxy: 10.0.0.100:54321
In the preceding example, 10.0.0.100 is a proxy server. If the server is a trusted proxy, add the server's IP address to KnownProxies
, or add a trusted network to KnownNetworks
. For more information, see the Forwarded Headers Middleware options section.
using Microsoft.AspNetCore.HttpOverrides;
using System.Net;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders =
ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
options.KnownProxies.Add(IPAddress.Parse("10.0.0.100"));
});
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseForwardedHeaders();
app.UseHsts();
}
else
{
app.UseDeveloperExceptionPage();
app.UseForwardedHeaders();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
To display the logs, add "Microsoft.AspNetCore.HttpLogging": "Information"
to the appsettings.Development.json
file:
{
"DetailedErrors": true,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.AspNetCore.HttpLogging": "Information"
}
}
}
Important
Only allow trusted proxies and networks to forward headers. Otherwise, IP spoofing attacks are possible.
Additional resources
In the recommended configuration for ASP.NET Core, the app is hosted using IIS/ASP.NET Core Module, Nginx, or Apache. Proxy servers, load balancers, and other network appliances often obscure information about the request before it reaches the app:
- When HTTPS requests are proxied over HTTP, the original scheme (HTTPS) is lost and must be forwarded in a header.
- Because an app receives a request from the proxy and not its true source on the Internet or corporate network, the originating client IP address must also be forwarded in a header.
This information may be important in request processing, for example in redirects, authentication, link generation, policy evaluation, and client geolocation.
Forwarded headers
By convention, proxies forward information in HTTP headers.
Header | Description |
---|---|
X-Forwarded-For | Holds information about the client that initiated the request and subsequent proxies in a chain of proxies. This parameter may contain IP addresses (and, optionally, port numbers). In a chain of proxy servers, the first parameter indicates the client where the request was first made. Subsequent proxy identifiers follow. The last proxy in the chain isn't in the list of parameters. The last proxy's IP address, and optionally a port number, are available as the remote IP address at the transport layer. |
X-Forwarded-Proto | The value of the originating scheme (HTTP/HTTPS). The value may also be a list of schemes if the request has traversed multiple proxies. |
X-Forwarded-Host | The original value of the Host header field. Usually, proxies don't modify the Host header. See Microsoft Security Advisory CVE-2018-0787 for information on an elevation-of-privileges vulnerability that affects systems where the proxy doesn't validate or restrict Host headers to known good values. |
The Forwarded Headers Middleware (ForwardedHeadersMiddleware), reads these headers and fills in the associated fields on HttpContext.
The middleware updates:
- HttpContext.Connection.RemoteIpAddress: Set using the
X-Forwarded-For
header value. Additional settings influence how the middleware setsRemoteIpAddress
. For details, see the Forwarded Headers Middleware options. The consumed values are removed fromX-Forwarded-For
, and the old values are persisted inX-Original-For
. The same pattern is applied to the other headers,Host
andProto
. - HttpContext.Request.Scheme: Set using the
X-Forwarded-Proto
header value. - HttpContext.Request.Host: Set using the
X-Forwarded-Host
header value.
For more information on the preceding, see this GitHub issue.
Forwarded Headers Middleware default settings can be configured. For the default settings:
- There is only one proxy between the app and the source of the requests.
- Only loopback addresses are configured for known proxies and known networks.
- The forwarded headers are named
X-Forwarded-For
andX-Forwarded-Proto
. - The
ForwardedHeaders
value isForwardedHeaders.None
, the desired forwarders must be set here to enable the middleware.
Not all network appliances add the X-Forwarded-For
and X-Forwarded-Proto
headers without additional configuration. Consult your appliance manufacturer's guidance if proxied requests don't contain these headers when they reach the app. If the appliance uses different header names than X-Forwarded-For
and X-Forwarded-Proto
, set the ForwardedForHeaderName and ForwardedProtoHeaderName options to match the header names used by the appliance. For more information, see Forwarded Headers Middleware options and Configuration for a proxy that uses different header names.
IIS/IIS Express and ASP.NET Core Module
Forwarded Headers Middleware is enabled by default by IIS Integration Middleware when the app is hosted out-of-process behind IIS and the ASP.NET Core Module. Forwarded Headers Middleware is activated to run first in the middleware pipeline with a restricted configuration specific to the ASP.NET Core Module due to trust concerns with forwarded headers (for example, IP spoofing). The middleware is configured to forward the X-Forwarded-For
and X-Forwarded-Proto
headers and is restricted to a single localhost proxy. If additional configuration is required, see the Forwarded Headers Middleware options.
Other proxy server and load balancer scenarios
Outside of using IIS Integration when hosting out-of-process, Forwarded Headers Middleware isn't enabled by default. Forwarded Headers Middleware must be enabled for an app to process forwarded headers with UseForwardedHeaders. After enabling the middleware if no ForwardedHeadersOptions are specified to the middleware, the default ForwardedHeadersOptions.ForwardedHeaders are ForwardedHeaders.None.
Configure the middleware with ForwardedHeadersOptions to forward the X-Forwarded-For
and X-Forwarded-Proto
headers in Startup.ConfigureServices
.
Forwarded Headers Middleware order
Forwarded Headers Middleware should run before other middleware. This ordering ensures that the middleware relying on forwarded headers information can consume the header values for processing. Forwarded Headers Middleware can run after diagnostics and error handling, but it must be run before calling UseHsts
:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders =
ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseForwardedHeaders();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseForwardedHeaders();
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}
Alternatively, call UseForwardedHeaders
before diagnostics:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseForwardedHeaders();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
Note
If no ForwardedHeadersOptions are specified in Startup.ConfigureServices
or directly to the extension method with UseForwardedHeaders, the default headers to forward are ForwardedHeaders.None. The ForwardedHeaders property must be configured with the headers to forward.
Nginx configuration
To forward the X-Forwarded-For
and X-Forwarded-Proto
headers, see Host ASP.NET Core on Linux with Nginx. For more information, see NGINX: Using the Forwarded header.
Apache configuration
X-Forwarded-For
is added automatically (see Apache Module mod_proxy: Reverse Proxy Request Headers).
Forwarded Headers Middleware options
ForwardedHeadersOptions control the behavior of the Forwarded Headers Middleware. The following example changes the default values:
- Limit the number of entries in the forwarded headers to
2
. - Add a known proxy address of
127.0.10.1
. - Change the forwarded header name from the default
X-Forwarded-For
toX-Forwarded-For-My-Custom-Header-Name
.
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardLimit = 2;
options.KnownProxies.Add(IPAddress.Parse("127.0.10.1"));
options.ForwardedForHeaderName = "X-Forwarded-For-My-Custom-Header-Name";
});
Option | Description |
---|---|
AllowedHosts | Restricts hosts by the X-Forwarded-Host header to the values provided.
IList<string> . |
ForwardedHeaders | Identifies which forwarders should be processed. See the ForwardedHeaders Enum for the list of fields that apply. Typical values assigned to this property are ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto .The default value is ForwardedHeaders.None. |
ForwardedForHeaderName | Use the header specified by this property instead of the one specified by ForwardedHeadersDefaults.XForwardedForHeaderName. This option is used when the proxy/forwarder doesn't use the X-Forwarded-For header but uses some other header to forward the information.The default is X-Forwarded-For . |
ForwardedHostHeaderName | Use the header specified by this property instead of the one specified by ForwardedHeadersDefaults.XForwardedHostHeaderName. This option is used when the proxy/forwarder doesn't use the X-Forwarded-Host header but uses some other header to forward the information.The default is X-Forwarded-Host . |
ForwardedProtoHeaderName | Use the header specified by this property instead of the one specified by ForwardedHeadersDefaults.XForwardedProtoHeaderName. This option is used when the proxy/forwarder doesn't use the X-Forwarded-Proto header but uses some other header to forward the information.The default is X-Forwarded-Proto . |
ForwardedPrefixHeaderName | Use the header specified by this property instead of the one specified by ForwardedHeadersDefaults.XForwardedPrefixHeaderName. This option is used when the proxy/forwarder doesn't use the X-Forwarded-Prefix header but uses some other header to forward the information.The default is X-Forwarded-Prefix . |
ForwardLimit | Limits the number of entries in the headers that are processed. Set to null to disable the limit, but this should only be done if KnownProxies or KnownNetworks are configured. Setting a non-null value is a precaution (but not a guarantee) to protect against misconfigured proxies and malicious requests arriving from side-channels on the network.Forwarded Headers Middleware processes headers in reverse order from right to left. If the default value ( 1 ) is used, only the rightmost value from the headers is processed unless the value of ForwardLimit is increased.The default is 1 . |
KnownNetworks | Address ranges of known networks to accept forwarded headers from. Provide IP ranges using Classless Interdomain Routing (CIDR) notation. If the server is using dual-mode sockets, IPv4 addresses are supplied in an IPv6 format (for example, 10.0.0.1 in IPv4 represented in IPv6 as ::ffff:10.0.0.1 ). See IPAddress.MapToIPv6. Determine if this format is required by looking at the HttpContext.Connection.RemoteIpAddress.The default is an IList <IPNetwork> containing a single entry for new IPNetwork(IPAddress.Loopback, 8) . |
KnownProxies | Addresses of known proxies to accept forwarded headers from. Use KnownProxies to specify exact IP address matches.If the server is using dual-mode sockets, IPv4 addresses are supplied in an IPv6 format (for example, 10.0.0.1 in IPv4 represented in IPv6 as ::ffff:10.0.0.1 ). See IPAddress.MapToIPv6. Determine if this format is required by looking at the HttpContext.Connection.RemoteIpAddress.The default is an IList <IPAddress> containing a single entry for IPAddress.IPv6Loopback . |
OriginalForHeaderName | Use the header specified by this property instead of the one specified by ForwardedHeadersDefaults.XOriginalForHeaderName. The default is X-Original-For . |
OriginalHostHeaderName | Use the header specified by this property instead of the one specified by ForwardedHeadersDefaults.XOriginalHostHeaderName. The default is X-Original-Host . |
OriginalProtoHeaderName | Use the header specified by this property instead of the one specified by ForwardedHeadersDefaults.XOriginalProtoHeaderName. The default is X-Original-Proto . |
OriginalPrefixHeaderName | Use the header specified by this property instead of the one specified by ForwardedHeadersDefaults.XOriginalPrefixHeaderName. The default is X-Original-Prefix . |
RequireHeaderSymmetry | Require the number of header values to be in sync between the ForwardedHeadersOptions.ForwardedHeaders being processed. The default in ASP.NET Core 1.x is true . The default in ASP.NET Core 2.0 or later is false . |
Scenarios and use cases
When it isn't possible to add forwarded headers and all requests are secure
In some cases, it might not be possible to add forwarded headers to the requests proxied to the app. If the proxy is enforcing that all public external requests are HTTPS, the scheme can be manually set in Startup.Configure
before using any type of middleware:
app.Use((context, next) =>
{
context.Request.Scheme = "https";
return next();
});
This code can be disabled with an environment variable or other configuration setting in a development or staging environment.
Deal with path base and proxies that change the request path
Some proxies pass the path intact but with an app base path that should be removed so that routing works properly. UsePathBaseExtensions.UsePathBase middleware splits the path into HttpRequest.Path and the app base path into HttpRequest.PathBase.
If /foo
is the app base path for a proxy path passed as /foo/api/1
, the middleware sets Request.PathBase
to /foo
and Request.Path
to /api/1
with the following command:
app.UsePathBase("/foo");
The original path and path base are reapplied when the middleware is called again in reverse. For more information on middleware order processing, see ASP.NET Core Middleware.
If the proxy trims the path (for example, forwarding /foo/api/1
to /api/1
), fix redirects and links by setting the request's PathBase property:
app.Use((context, next) =>
{
context.Request.PathBase = new PathString("/foo");
return next();
});
If the proxy is adding path data, discard part of the path to fix redirects and links by using StartsWithSegments and assigning to the Path property:
app.Use((context, next) =>
{
if (context.Request.Path.StartsWithSegments("/foo", out var remainder))
{
context.Request.Path = remainder;
}
return next();
});
Configuration for a proxy that uses different header names
If the proxy doesn't use headers named X-Forwarded-For
and X-Forwarded-Proto
to forward the proxy address/port and originating scheme information, set the ForwardedForHeaderName and ForwardedProtoHeaderName options to match the header names used by the proxy:
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedForHeaderName = "Header_Name_Used_By_Proxy_For_X-Forwarded-For_Header";
options.ForwardedProtoHeaderName = "Header_Name_Used_By_Proxy_For_X-Forwarded-Proto_Header";
});
Forward the scheme for Linux and non-IIS reverse proxies
Apps that call UseHttpsRedirection and UseHsts put a site into an infinite loop if deployed to an Azure Linux App Service, Azure Linux virtual machine (VM), or behind any other reverse proxy besides IIS. TLS is terminated by the reverse proxy, and Kestrel isn't made aware of the correct request scheme. OAuth and OIDC also fail in this configuration because they generate incorrect redirects. UseIISIntegration adds and configures Forwarded Headers Middleware when running behind IIS, but there's no matching automatic configuration for Linux (Apache or Nginx integration).
To forward the scheme from the proxy in non-IIS scenarios, add and configure Forwarded Headers Middleware. In Startup.ConfigureServices
, use the following code:
// using Microsoft.AspNetCore.HttpOverrides;
if (string.Equals(
Environment.GetEnvironmentVariable("ASPNETCORE_FORWARDEDHEADERS_ENABLED"),
"true", StringComparison.OrdinalIgnoreCase))
{
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor |
ForwardedHeaders.XForwardedProto;
// Only loopback proxies are allowed by default.
// Clear that restriction because forwarders are enabled by explicit
// configuration.
options.KnownNetworks.Clear();
options.KnownProxies.Clear();
});
}
Certificate forwarding
Azure
To configure Azure App Service for certificate forwarding, see Configure TLS mutual authentication for Azure App Service. The following guidance pertains to configuring the ASP.NET Core app.
In Startup.Configure
, add the following code before the call to app.UseAuthentication();
:
app.UseCertificateForwarding();
Configure Certificate Forwarding Middleware to specify the header name that Azure uses. In Startup.ConfigureServices
, add the following code to configure the header from which the middleware builds a certificate:
services.AddCertificateForwarding(options =>
options.CertificateHeader = "X-ARR-ClientCert");
Other web proxies
If a proxy is used that isn't IIS or Azure App Service's Application Request Routing (ARR), configure the proxy to forward the certificate that it received in an HTTP header. In Startup.Configure
, add the following code before the call to app.UseAuthentication();
:
app.UseCertificateForwarding();
Configure the Certificate Forwarding Middleware to specify the header name. In Startup.ConfigureServices
, add the following code to configure the header from which the middleware builds a certificate:
services.AddCertificateForwarding(options =>
options.CertificateHeader = "YOUR_CERTIFICATE_HEADER_NAME");
If the proxy isn't base64-encoding the certificate (as is the case with Nginx), set the HeaderConverter
option. Consider the following example in Startup.ConfigureServices
:
services.AddCertificateForwarding(options =>
{
options.CertificateHeader = "YOUR_CUSTOM_HEADER_NAME";
options.HeaderConverter = (headerValue) =>
{
var clientCertificate =
/* some conversion logic to create an X509Certificate2 */
return clientCertificate;
}
});
Troubleshoot
When headers aren't forwarded as expected, enable logging. If the logs don't provide sufficient information to troubleshoot the problem, enumerate the request headers received by the server. Use inline middleware to write request headers to an app response or log the headers.
To write the headers to the app's response, place the following terminal inline middleware immediately after the call to UseForwardedHeaders in Startup.Configure
:
app.Run(async (context) =>
{
context.Response.ContentType = "text/plain";
// Request method, scheme, and path
await context.Response.WriteAsync(
$"Request Method: {context.Request.Method}{Environment.NewLine}");
await context.Response.WriteAsync(
$"Request Scheme: {context.Request.Scheme}{Environment.NewLine}");
await context.Response.WriteAsync(
$"Request Path: {context.Request.Path}{Environment.NewLine}");
// Headers
await context.Response.WriteAsync($"Request Headers:{Environment.NewLine}");
foreach (var header in context.Request.Headers)
{
await context.Response.WriteAsync($"{header.Key}: " +
$"{header.Value}{Environment.NewLine}");
}
await context.Response.WriteAsync(Environment.NewLine);
// Connection: RemoteIp
await context.Response.WriteAsync(
$"Request RemoteIp: {context.Connection.RemoteIpAddress}");
});
You can write to logs instead of the response body. Writing to logs allows the site to function normally while debugging.
To write logs rather than to the response body:
- Inject
ILogger<Startup>
into theStartup
class as described in Create logs in Startup. - Place the following inline middleware immediately after the call to UseForwardedHeaders in
Startup.Configure
.
app.Use(async (context, next) =>
{
// Request method, scheme, path, and base path
_logger.LogDebug("Request Method: {Method}", context.Request.Method);
_logger.LogDebug("Request Scheme: {Scheme}", context.Request.Scheme);
_logger.LogDebug("Request Path: {Path}", context.Request.Path);
_logger.LogDebug("Request Path Base: {PathBase}", context.Request.PathBase);
// Headers
foreach (var header in context.Request.Headers)
{
_logger.LogDebug("Header: {Key}: {Value}", header.Key, header.Value);
}
// Connection: RemoteIp
_logger.LogDebug("Request RemoteIp: {RemoteIpAddress}",
context.Connection.RemoteIpAddress);
await next();
});
When processed, X-Forwarded-{For|Proto|Host|Prefix}
values are moved to X-Original-{For|Proto|Host|Prefix}
. If there are multiple values in a given header, Forwarded Headers Middleware processes headers in reverse order from right to left. The default ForwardLimit
is 1
(one), so only the rightmost value from the headers is processed unless the value of ForwardLimit
is increased.
The request's original remote IP must match an entry in the KnownProxies
or KnownNetworks
lists before forwarded headers are processed. This limits header spoofing by not accepting forwarders from untrusted proxies. When an unknown proxy is detected, logging indicates the address of the proxy:
September 20th 2018, 15:49:44.168 Unknown proxy: 10.0.0.100:54321
In the preceding example, 10.0.0.100 is a proxy server. If the server is a trusted proxy, add the server's IP address to KnownProxies
(or add a trusted network to KnownNetworks
) in Startup.ConfigureServices
. For more information, see the Forwarded Headers Middleware options section.
services.Configure<ForwardedHeadersOptions>(options =>
{
options.KnownProxies.Add(IPAddress.Parse("10.0.0.100"));
});
Important
Only allow trusted proxies and networks to forward headers. Otherwise, IP spoofing attacks are possible.
Additional resources
ASP.NET Core