ASP.NET - ASP.NET Core Forwarded Headers and Reverse Proxy Integration
When an ASP.NET Core application is deployed behind a reverse proxy or load balancer, the application does not always receive the original details of the client's request directly. Instead, the proxy receives the request first and then forwards it to the ASP.NET Core application. This can cause the application to see the proxy's IP address, HTTP scheme, or internal host rather than the information associated with the original client request.
ASP.NET Core provides Forwarded Headers Middleware to solve this problem. It reads specific HTTP headers added by trusted proxies and uses them to restore information about the original request. Microsoft specifically recommends configuring this middleware when ASP.NET Core is running behind proxies or load balancers.
1. What Is a Reverse Proxy?
A reverse proxy is a server that sits between users and an application server.
A typical architecture looks like this:
Client
|
| HTTPS request
v
Reverse Proxy
|
| HTTP/HTTPS request
v
ASP.NET Core Application
The client may access:
https://www.example.com
However, the reverse proxy may communicate with the ASP.NET Core application through:
http://10.0.0.20:5000
From the application's perspective, the immediate connection is coming from the reverse proxy rather than directly from the client.
Common reverse proxies and infrastructure components include:
-
Nginx
-
Apache
-
IIS
-
Cloud load balancers
-
Application gateways
-
Kubernetes ingress controllers
-
Other network proxies
Reverse proxies are commonly used for SSL/TLS termination, load balancing, routing, security, and exposing applications through a public domain.
2. Why Does ASP.NET Core Need Forwarded Headers?
Consider a user accessing an application using HTTPS:
Client
https://example.com
|
v
Reverse Proxy
|
v
ASP.NET Core
The reverse proxy may terminate the HTTPS connection and communicate with the ASP.NET Core application over HTTP.
The application could therefore see:
Request.Scheme = "http"
even though the user originally accessed:
https://example.com
This difference can cause problems with HTTPS redirection, authentication, URL generation, cookies, and external authentication providers. Microsoft notes that incorrect forwarded-header configuration can even result in redirect loops when HTTPS redirection is used.
The same problem can occur with the client's IP address.
Instead of seeing:
203.0.113.25
the ASP.NET Core application may see the reverse proxy's address:
10.0.0.100
Forwarded headers allow the proxy to communicate the original request information to the application.
3. Important Forwarded Headers
Several headers are commonly used.
X-Forwarded-For
X-Forwarded-For contains information about the original client IP address and potentially the addresses of proxies through which the request has passed.
For example:
X-Forwarded-For: 203.0.113.25
ASP.NET Core can use this information to populate:
HttpContext.Connection.RemoteIpAddress
This is particularly useful when applications need the client's IP address for logging, auditing, rate-limiting, or other request-processing purposes.
X-Forwarded-Proto
X-Forwarded-Proto identifies the original request scheme.
For example:
X-Forwarded-Proto: https
ASP.NET Core can use this information to set:
HttpContext.Request.Scheme
This allows the application to understand that the original request was HTTPS even if the proxy communicated with the application using HTTP.
X-Forwarded-Host
X-Forwarded-Host contains the original host requested by the client.
For example:
X-Forwarded-Host: www.example.com
ASP.NET Core can use this information to populate the request host.
X-Forwarded-Prefix
X-Forwarded-Prefix can be used when an application is exposed under a path prefix through a proxy.
For example:
https://example.com/myapp
The proxy can communicate the /myapp prefix to the application through this header.
ASP.NET Core's forwarded-header middleware supports these forwarded values and maps them to the corresponding properties of HttpContext.
4. Forwarded Headers Middleware
ASP.NET Core provides the UseForwardedHeaders() middleware for processing these headers.
A basic configuration looks like this:
using Microsoft.AspNetCore.HttpOverrides;
var builder = WebApplication.CreateBuilder(args);
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders =
ForwardedHeaders.XForwardedFor |
ForwardedHeaders.XForwardedProto;
});
var app = builder.Build();
app.UseForwardedHeaders();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
The important part is:
options.ForwardedHeaders =
ForwardedHeaders.XForwardedFor |
ForwardedHeaders.XForwardedProto;
This tells ASP.NET Core which forwarded headers should be processed.
Without configuring the desired forwarded headers, the default ForwardedHeaders value is None.
5. Middleware Ordering
The position of UseForwardedHeaders() in the middleware pipeline is important.
For example:
app.UseForwardedHeaders();
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
Forwarded headers should generally be processed before middleware that depends on the original request information.
For example, HTTPS redirection needs to know whether the original request was HTTP or HTTPS. If the forwarded scheme has not been processed first, the application can incorrectly believe that an HTTPS request was HTTP.
Microsoft recommends placing forwarded-header processing before UseHsts() and other middleware that relies on the forwarded request information.
6. Reverse Proxy Example
Suppose a user sends:
GET https://example.com/products
The request reaches a reverse proxy.
The proxy forwards something similar to:
GET http://10.0.0.20:5000/products
X-Forwarded-For: 203.0.113.25
X-Forwarded-Proto: https
X-Forwarded-Host: example.com
Without forwarded-header processing, ASP.NET Core might see:
RemoteIpAddress = 10.0.0.100
Scheme = http
Host = 10.0.0.20:5000
After forwarded headers are correctly processed, the application can see values corresponding to the original request:
RemoteIpAddress = 203.0.113.25
Scheme = https
Host = example.com
This makes the application behave as though it understands the public-facing request even though a proxy sits between the client and application.
7. Security Considerations
Forwarded headers should not automatically be trusted from arbitrary clients.
This is one of the most important aspects of reverse-proxy configuration.
An attacker could potentially send a request containing a forged header such as:
X-Forwarded-For: 1.2.3.4
If the application blindly trusts this value, it could incorrectly treat the attacker as coming from another IP address.
ASP.NET Core therefore provides mechanisms such as:
KnownProxies
and:
KnownNetworks
These allow applications to specify which proxies or networks are trusted to provide forwarded-header information. Microsoft specifically warns that only trusted proxies and networks should be permitted to forward these headers because otherwise IP spoofing can occur.
For example:
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders =
ForwardedHeaders.XForwardedFor |
ForwardedHeaders.XForwardedProto;
options.KnownProxies.Add(
IPAddress.Parse("10.0.0.100"));
});
Here, 10.0.0.100 is explicitly identified as a trusted proxy.
8. ForwardLimit
Applications can also control how many forwarded values are processed.
For example:
options.ForwardLimit = 2;
This can be useful when requests pass through multiple proxy servers.
A request might travel like this:
Client
|
Proxy 1
|
Proxy 2
|
ASP.NET Core
The forwarded headers could contain information from multiple hops.
ASP.NET Core's default ForwardLimit is 1. When multiple proxy hops are expected, the configuration needs to reflect the actual trusted infrastructure.
9. Forwarded Headers Behind Nginx
A common deployment scenario is:
Internet
|
v
Nginx
|
v
ASP.NET Core / Kestrel
Nginx acts as the public-facing reverse proxy while Kestrel runs the ASP.NET Core application.
The proxy should forward the necessary information, while ASP.NET Core should be configured to process it.
For example, the application can configure:
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders =
ForwardedHeaders.XForwardedFor |
ForwardedHeaders.XForwardedProto;
});
var app = builder.Build();
app.UseForwardedHeaders();
This allows ASP.NET Core to correctly interpret information about the original connection.
10. Problems Caused by Incorrect Configuration
Incorrect forwarded-header configuration can produce several unexpected behaviors.
Incorrect client IP
Application logs may record the proxy's IP address instead of the client's address.
HTTPS redirect loops
The proxy receives HTTPS but communicates with the application through HTTP. If ASP.NET Core doesn't know that the original request was HTTPS, HTTPS redirection can repeatedly redirect the request.
Incorrect generated URLs
Applications that generate absolute URLs may produce:
http://example.com
instead of:
https://example.com
Authentication problems
OAuth and OpenID Connect authentication flows rely heavily on correct redirect URLs. Incorrect scheme or host information can cause authentication callbacks to fail. Microsoft specifically identifies incorrect redirects as a consequence of improper proxy configuration.
Incorrect host information
If the application uses the internal host supplied by the proxy instead of the public host, generated links and redirects may point to an internal server address.
11. Forwarded Headers and HTTPS
One particularly important use case is HTTPS.
Consider:
Browser
|
HTTPS
|
Reverse Proxy
|
HTTP
|
ASP.NET Core
The browser is using HTTPS, but the ASP.NET Core server receives HTTP.
The proxy can send:
X-Forwarded-Proto: https
ASP.NET Core then processes this information and updates:
HttpContext.Request.Scheme
to reflect HTTPS.
This is why forwarded headers should be processed before HTTPS-related middleware. Microsoft specifically recommends this ordering for applications behind reverse proxies.
12. Difference Between Proxy and Reverse Proxy
A forward proxy generally acts on behalf of the client:
Client
|
Forward Proxy
|
Internet
A reverse proxy acts on behalf of the server or application:
Internet
|
Reverse Proxy
|
Application
ASP.NET Core applications commonly encounter reverse proxies because production systems frequently place a proxy or load balancer in front of application servers.
13. Real-World Architecture
A larger production architecture could look like:
Internet
|
v
Load Balancer
|
+--------+--------+
| |
v v
Proxy Server Proxy Server
| |
v v
ASP.NET Core ASP.NET Core
Server 1 Server 2
In this situation, the application needs to correctly understand forwarded request information regardless of which server receives the request.
This becomes especially important when applications are deployed in cloud environments, containers, Kubernetes clusters, or web farms.
14. Best Practices
When using ASP.NET Core behind a reverse proxy, follow these principles:
-
Identify exactly which proxy or load balancer is in front of the application.
-
Determine which forwarded headers the proxy generates.
-
Configure
ForwardedHeadersOptionsaccordingly. -
Call
UseForwardedHeaders()early in the middleware pipeline. -
Configure
KnownProxiesorKnownNetworkswhere appropriate. -
Do not blindly trust forwarded headers from untrusted clients.
-
Verify HTTPS behavior after deployment.
-
Test authentication redirects when using OAuth or OpenID Connect.
-
Test generated absolute URLs.
-
Verify that application logs record the intended client IP.
15. Summary
ASP.NET Core Forwarded Headers and Reverse Proxy Integration is the process of making an ASP.NET Core application correctly understand information about the original client request when the application is located behind a reverse proxy or load balancer.
The most important headers are:
X-Forwarded-For
X-Forwarded-Proto
X-Forwarded-Host
X-Forwarded-Prefix
ASP.NET Core's forwarded-header middleware reads these values and updates the corresponding request information, such as the remote IP address, scheme, host, and path base.
The key principle is that forwarded headers improve the application's understanding of the original request, but they must only be trusted when they originate from trusted proxy infrastructure. Proper configuration prevents problems with client IP detection, HTTPS redirects, authentication, URL generation, and other proxy-dependent functionality.