---
title: "Configuring ASP.NET Core Forwarded Headers Middleware"
description: "Configure ASP.NET Core Forwarded Headers behind a reverse proxy: headers to process, proxies to trust, ASPNETCORE_FORWARDEDHEADERS_ENABLED and AllowedHosts."
url: https://nestenius.se/net/configuring-asp-net-core-forwarded-headers-middleware/
date: 2025-04-10
updated: 2026-10-19
author: "Tore Nestenius"
category: ".NET"
site: https://nestenius.se/
---

# Configuring ASP.NET Core Forwarded Headers Middleware

In my [previous blog post](https://nestenius.se/net/exploring-the-forwarded-headers-middleware-in-asp-net-core/), I explained what the Forwarded Headers Middleware does and why it matters. In this post, I will show you how to add it to your application, configure it, and point out a few common issues that can arise in configuring the forwarded headers middleware.

In this blog post:

- [Testing the Forwarded Headers Application](#testing-the-forwarded-headers-application)
- [Adding the ForwardHeaders Middleware](#adding-the-forwardheaders-middleware)
- [Configuring ForwardHeadersMiddleware](#configuring-forwardheadersmiddleware)
- [ForwardHeaders Middleware in Action](#forwardheaders-middleware-in-action)
- [Observing the Problem in the Logs](#observing-the-problem-in-the-logs)
- [Allowed Forwarded Headers Hosts](#allowed-forwarded-headers-hosts)
- [The ASPNETCORE\_FORWARDEDHEADERS\_ENABLED variable](#the-aspnetcore_forwardedheaders_enabled-variable)
- [Forwarded Headers in the Cloud Debugger](#forwarded-headers-in-the-cloud-debugger)
- [Source Code](#source-code)
- [Summary](#summary)

## The Sample ForwardHeaders Application

Let’s start with a new ASP.NET Core project using the empty template:

![ASP.NET Core Empty Project Template](https://nestenius.se/_astro/aspnet-core-empty-project-template-selection.D7uU_h_U_5RgCe.webp)

Then I updated **launchSettings.json** to listen on ports **5000** and **5001**:

```json
"applicationUrl": "https://localhost:5001;http://localhost:5000",
```

After that, I replaced the default **app.MapGet()** with this endpoint:

```csharp
app.MapGet("{**route}", async (HttpContext context,
                              IOptions<ForwardedHeadersOptions> forwardedOptions) =>
{
    var requestDetails = new
    {
        Protocol = context.Request.Protocol,
        Scheme = context.Request.Scheme,
        Path = context.Request.Path.Value,
        PathBase = context.Request.PathBase.Value,
        Host = context.Request.Host.ToString(),
        DisplayUrl = context.Request.GetDisplayUrl(),
        RemoteIpAddress = context.Connection.RemoteIpAddress?.ToString(),
        Headers = context.Request.Headers
                  .ToDictionary(h => h.Key, h => h.Value.ToString()),
        ForwardedHeaders = new
        {
            ForwardedHeaders = GetEnabledFlags(forwardedOptions.Value.ForwardedHeaders),
            AllowedHosts = forwardedOptions.Value.AllowedHosts.ToList(),
            KnownNetworks = forwardedOptions.Value.KnownNetworks
                            .Select(n => n.Prefix + "/" + n.PrefixLength)
                            .ToList(),
            KnownProxies = forwardedOptions.Value.KnownProxies
                            .Select(p => p.ToString()).ToList(),
            forwardedOptions.Value.ForwardLimit,
            forwardedOptions.Value.OriginalForHeaderName,
            forwardedOptions.Value.OriginalHostHeaderName,
            forwardedOptions.Value.OriginalProtoHeaderName,
            forwardedOptions.Value.OriginalPrefixHeaderName,
            forwardedOptions.Value.ForwardedForHeaderName,
            forwardedOptions.Value.ForwardedHostHeaderName,
            forwardedOptions.Value.ForwardedProtoHeaderName,
            forwardedOptions.Value.ForwardedPrefixHeaderName
        }
    };
    context.Response.ContentType = "application/json";
    await context.Response.WriteAsync(JsonSerializer.Serialize(requestDetails));
});
```

A small helper method is also added:

```csharp
static List<string> GetEnabledFlags(ForwardedHeaders headers)
{
    return Enum.GetValues<ForwardedHeaders>()
    	.Where(flag => flag != ForwardedHeaders.None && headers.HasFlag(flag))
		.Select(flag => flag.ToString())         
		.ToList(); 
}
```

When called, the endpoint responds to any request and returns the request details as seen by ASP.NET Core. It’s a simple and effective way to inspect how the middleware works. The response also includes the current configuration for the Forwarded Headers Middleware.

## Testing the Forwarded Headers Application

To test it, we can add a Visual Studio [.http file](https://learn.microsoft.com/en-us/aspnet/core/test/http-files?view=aspnetcore-9.0&source=docs) like this one:

```http
GET http://localhost:5000 HTTP/1.1 
X-Forwarded-For: 123.0.123.1 
X-Forwarded-Proto: https 
X-Forwarded-Host: nestenius.se
```

This lets us send requests with custom headers. To send a request to the application, you just need to start it and click the **Send request** link:

![Sample HTTP request with the X-Forwarded-\* headers](https://nestenius.se/_astro/http-request-with-x-forwarded-headers-in-jetbrains-rider.DkWaAH52_ZpctfF.webp)

Since we haven’t added the Forwarded Headers middleware yet, the response will look like this:

```json
{
  "Protocol": "HTTP/1.1",
  "Scheme": "http",
  "Path": "/",
  "PathBase": "",
  "Host": "localhost:5000",
  "DisplayUrl": "http://localhost:5000/",
  "RemoteIpAddress": "::1",
  "Headers": {
    "Host": "localhost:5000",
    "X-Forwarded-For": "123.0.123.1",
    "X-Forwarded-Proto": "https",
    "X-Forwarded-Host": "nestenius.se"
  },
  "ForwardedHeaders": {
    "ForwardedHeaders": [],
    "AllowedHosts": [],
    "KnownNetworks": [
      "127.0.0.1/8"
    ],
    "KnownProxies": [
      "::1"
    ],
    "ForwardLimit": 1,
    "OriginalForHeaderName": "X-Original-For",
    "OriginalHostHeaderName": "X-Original-Host",
    "OriginalProtoHeaderName": "X-Original-Proto",
    "OriginalPrefixHeaderName": "X-Original-Prefix",
    "ForwardedForHeaderName": "X-Forwarded-For",
    "ForwardedHostHeaderName": "X-Forwarded-Host",
    "ForwardedProtoHeaderName": "X-Forwarded-Proto",
    "ForwardedPrefixHeaderName": "X-Forwarded-Prefix"
  }
}
```

From this response, we can tell that the middleware either isn’t present or has chosen to ignore the request based on its current configuration. One clear sign is that none of the **X-Original-\*** headers were added. These headers are only set when the middleware rewrites the request using the forwarded values, which didn’t happen here.

#### **Why not use the browser?**

While you can use a browser to send basic requests, it won’t let you set custom headers like **X-Forwarded-For** or **X-Forwarded-Host**. Browsers restrict which headers can be set for security reasons. That’s why tools like [.http](https://learn.microsoft.com/en-us/aspnet/core/test/http-files?view=aspnetcore-9.0&source=docs&WT.mc_id=email&sharingId=MVP_427602) files, [Bruno](https://www.usebruno.com/), or [Fiddler](https://getfiddler.com) are much more suitable for this kind of testing. Because they let you define exactly which headers to include in your request.

#### What about the X-Forwarded-Prefix header?

This header deserves a blog post of its own. It's closely tied to how PathBase works in ASP.NET Core, and things can get tricky when you’re running apps behind reverse proxies. If you want to dig deeper into how PathBase works, I recommend Andrew Lock’s excellent post: [Understanding PathBase in ASP.NET Core](https://andrewlock.net/understanding-pathbase-in-aspnetcore/).

## Adding the ForwardHeaders Middleware

To start using the middleware, you first need to add it to the start of the request pipeline:

```csharp
app.UseForwardedHeaders();
```

But just adding it like this won’t do anything on its own. You also need to configure it. Here’s the starting point:

```csharp
builder.Services.Configure<ForwardedHeadersOptions>(options => 
{     
    //TODO: Add configuration here
	... 
});
```

If you run the application now and send a test request, the output will look exactly the same as before. Nothing has changed yet.

## Configuring ForwardHeadersMiddleware

By default, the middleware does not process any **X-Forwarded-\*** headers. We can confirm this by checking the response, which shows an empty **ForwardedHeaders** list:

```json
"ForwardedHeaders": [],
```

The **ForwardedHeaders** array is a core part of the middleware’s configuration. It lists which **X-Forwarded-\*** headers the middleware is currently set up to process. Right now, it’s empty, so no headers will be considered or used when rewriting the request.

We need to tell the middleware which headers it should read explicitly to fix this. The available headers are:

- **X-Forwarded-For**
- **X-Forwarded-Host**
- **X-Forwarded-Proto**
- **X-Forwarded-Prefix**

Some proxies use different header names. If that’s the case, you can change the expected names using these configuration options:

- **ForwardedForHeaderName** (Default "X-Forwarded-For")
- **ForwardedHostHeaderName** (Default "X-Forwarded-Host")
- **ForwardedProtoHeaderName** (Default "X-Forwarded-Proto")
- **ForwardedPrefixHeaderName** (Default "X-Forwarded-Prefix")

To enable all four headers, configure the middleware like this:

```csharp
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor |
                           ForwardedHeaders.XForwardedHost |
                           ForwardedHeaders.XForwardedProto |
                           ForwardedHeaders.XForwardedPrefix;
```

Or simply use the shortcut:

```csharp
options.ForwardedHeaders = ForwardedHeaders.All;
```

Both of these will enable the same functionality.

In our case, we don’t want to support the **X-Forwarded-Prefix** header, so we leave it out on purpose:

```csharp
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor |
                           ForwardedHeaders.XForwardedHost |                            
						   ForwardedHeaders.XForwardedProto;
```

Being explicit about which headers you want to support is a good practice.

### Why do we need to configure this?

It gives you control over which headers the middleware should support and which ones to ignore. Which headers you need to support depends on the proxy that you use and how your infrastructure is set up. Not all proxies send the same headers; some may only use a subset. Being explicit helps avoid misconfigurations and improves security.  

Now restart the application and send the request again. You should now see that the **ForwardedHeaders** array contains the expected headers:

```json
"ForwardedHeaders": [
   "XForwardedFor",   
   "XForwardedHost",   
   "XForwardedProto" 
],
```

## ForwardHeaders Middleware in Action

With the **ForwardedHeaders** option set, the middleware is now active. It will look for the headers you’ve configured and rewrite the request based on their values.

Let’s compare what the request looks like before and after enabling the middleware:

#### Before

```json
{
  "Protocol": "HTTP/1.1",
  "Scheme": "http",
  "Path": "/",
  "PathBase": "",
  "Host": "localhost:5000",
  "DisplayUrl": "http://localhost:5000/",
  "RemoteIpAddress": "::1",
  "Headers": {
    "Host": "localhost:5000",
    "X-Forwarded-For": "123.0.123.1",
    "X-Forwarded-Proto": "https",
    "X-Forwarded-Host": "nestenius.se"
  },
}
```

#### After

```json
{  
  "Protocol": "HTTP/1.1", 
  "Scheme": "https",
  "Path": "/",
  "PathBase": "",
  "Host": "nestenius.se",
  "DisplayUrl": "https://nestenius.se//",
  "RemoteIpAddress": "123.0.123.1",
  "Headers": {
    "Host": "nestenius.se",
    "X-Original-Proto": "http",    
	"X-Original-Host": "localhost:5000",    
	"X-Original-For": "[::1]:61781"
  },
}
```

The middleware has updated the **Scheme**, **Host**, and **RemoteIpAddress** fields using the values from the **X-Forwarded-\*** headers.  

At the same time, the original values have been preserved in the **X-Original-\*** headers. This lets your application know what was received and rewritten, which is helpful for diagnostics and auditing.

## ForwardHeadersMiddleware in Production

Let’s say you take the code above and deploy it to a production environment where a reverse proxy (like **NGINX**, **Traefik**, or a **load balancer**) sits between the users and your web application. This proxy is a separate service that forwards requests to your app and adds the appropriate **X-Forwarded-\*** headers.

**But now... it stops working. Why?**

This is the setup:

![ForwardedHeaders Architecture, a client and Proxy, that sends X-Forwarded-\* headers](https://nestenius.se/_astro/proxy-forwarded-headers-flow-diagram-aspnet-core.B5OLggrA_75Xim.webp)

The problem is that the middleware only trusts requests from known sources, meaning known proxies or networks. By default, it only trusts local addresses. If we look at the response from our application, we’ll see these two configuration settings that control this:

```text
"KnownNetworks": [
  "127.0.0.1/8"     
],     
"KnownProxies": 
[       
  "::1"
],
```

This means the forwarded headers will be ignored unless the request comes from either:

- An address in the **127.0.0.1/8** network range (IPv4 localhost)
- The specific address **::1** (IPv6 localhost)

You might wonder why the middleware doesn’t just trust all incoming addresses. Well, that would be a security risk! If any IP could forward headers, a malicious client could spoof values like the original IP address, scheme, or host, which could then break logging, access control, or other logic that depends on those details.

### How the Trust Check Works Internally

If we look at the [source code](https://github.com/dotnet/aspnetcore/blob/main/src/Middleware/HttpOverrides/src/ForwardedHeadersMiddleware.cs#L388) of the Forwarded Headers Middleware, we find a method that decides whether the IP address of the incoming request is allowed:

```csharp
private bool CheckKnownAddress(IPAddress address)
{
    if (address.IsIPv4MappedToIPv6)
    {
        var ipv4Address = address.MapToIPv4();
        if (CheckKnownAddress(ipv4Address))
        {
            return true;
        }
    }
    if (_options.KnownProxies.Contains(address))
    {
        return true;
    }
    foreach (var network in _options.KnownNetworks)
    {
        if (network.Contains(address))
        {
            return true;
        }
    }
    return false;
}
```

This code shows that a request is trusted if its IP address matches an entry in the **KnownProxies** list or falls within a configured **KnownNetworks** range.

### Testing Without a Real Proxy

How can we test the proxy’s behavior without setting up an entire proxy environment?  

One way is to fake it. You can add a simple middleware before **UseForwardedHeaders** that overrides the **RemoteIpAddress**. This lets you simulate a request coming from a specific proxy IP address. Here’s how that looks:

```csharp
app.Use(async (context, next) =>
{     
    // Spoof RemoteIpAddress      
	context.Connection.RemoteIpAddress = IPAddress.Parse("10.0.0.2");     
	await next(); 
}); 

app.UseForwardedHeaders();
```

If you now send a test request to the application, it will ignore the **X-Forwarded-\*** headers because the spoofed IP address (**10.0.0.2**) is not on the list of trusted proxies or networks.

## Observing the Problem in the Logs

When the Forwarded Headers Middleware rejects a request, it will write a debug entry to the log. It can also emit warnings for other issues, such as malformed headers or configuration problems. You need to increase the log level for the middleware’s namespace to see all of these messages.

In your [appsettings.json](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-9.0&WT.mc_id=email&sharingId=MVP_427602), update the logging section like this:

```json
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "Microsoft.AspNetCore.HttpOverrides": "Debug"
    }
  },
  "AllowedHosts": "*"
}
```

The key part is setting the log level for the [**Microsoft.AspNetCore.HttpOverrides**](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.httpoverrides?view=aspnetcore-9.0&WT.mc_id=email&sharingId=MVP_427602) namespace to **Debug**.This enables more logging from the Forwarded Headers Middleware.  

Now, if we run the application with the spoofed IP address in place, we should see a message like this in the console:

```yaml
dbug: Microsoft.AspNetCore.HttpOverrides.ForwardedHeadersMiddleware[1]
      Unknown proxy: 10.0.0.2:65529
```

This confirms that the forwarded headers were ignored because the request did not come from a trusted source.

## Fixing the Unknown proxy problem

To fix the issue, we need to tell the middleware which proxies or networks we trust. In our example, the proxy uses the IP address 10.0.0.2. 

We have two options:

#### Trust a specific proxy IP

To trust just one IP address, you can configure it like this:

```csharp
options.KnownProxies.Clear(); 
options.KnownProxies.Add(IPAddress.Parse("10.0.0.2"));
```

First, we clear the list to remove the default entry for localhost (::1). If you’re not using localhost in production, there's no reason to keep it trusted.

#### Trust a network range

If your proxy could use different IPs within a subnet, it's better to trust a full network range:

```csharp
options.KnownNetworks.Clear(); 
options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("10.0.0.0"), 24));
```

This allows traffic from the entire **10.0.0.0/24** range (**10.0.0.0** to **10.0.0.255**) to be trusted.

> **.NET 10 note:** In .NET 10, `ForwardedHeadersOptions.KnownNetworks` is obsolete and replaced by `KnownIPNetworks`, which uses `System.Net.IPNetwork` instead of the old `IPNetwork` type. The code above still works, but you will get obsolete warnings. This is the updated version (see the [Microsoft documentation](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.builder.forwardedheadersoptions.knownnetworks?view=aspnetcore-10.0)):
>
> ```csharp
> options.KnownIPNetworks.Clear();
> options.KnownIPNetworks.Add(System.Net.IPNetwork.Parse("10.0.0.0/24"));
> ```  

By adding either of these configurations, the middleware will now accept forwarded headers from your proxy at **10.0.0.2**.  

Restricting where these headers are accepted from really is critical. Trusting all of the sources would allow clients to spoof **X-Forwarded-\*** headers, which could affect security, logging, or routing behavior.

## Allowed Forwarded Headers Hosts

Another important part of the Forwarded Headers Middleware is how it handles the **X-Forwarded-Host** header.  

By default, the middleware will accept any value in the **X-Forwarded-Host** header. This means that a client or proxy could send in any hostname, and the middleware would apply it without restrictions.  

To tighten this risky behavior, you can use the **AllowedHosts** setting in the **ForwardedHeadersOptions** configuration.

```csharp
options.AllowedHosts.Clear();     
options.AllowedHosts.Add("nestenius.se");
```

With this configuration, only forwarded requests that include **X-Forwarded-Host: nestenius.se** will be accepted and applied. Any other value will be ignored.

## The ASPNETCORE\_FORWARDEDHEADERS\_ENABLED variable

If you’ve worked with ASP.NET Core in **Azure App Service** or **Azure Container Apps**, you might have encountered the **ASPNETCORE\_FORWARDEDHEADERS\_ENABLED** environment variable.

This environment variable controls whether ASP.NET Core will automatically add the Forwarded Headers Middleware to your application during startup.

Like when you use the default host builder like this:

```csharp
var builder = WebApplication.CreateBuilder(args);
```

If the **ASPNETCORE\_FORWARDEDHEADERS\_ENABLED** is set to true, ASP.NET Core will automatically call **UseForwardedHeaders()** behind the scenes. You don’t have to add it manually to the middleware pipeline.

This behavior is implemented in a component called **ForwardedHeadersStartupFilter** ([source code](https://github.com/dotnet/aspnetcore/blob/main/src/DefaultBuilder/src/ForwardedHeadersStartupFilter.cs)).

Okay, so let’s explore this next!

### Testing ASPNETCORE\_FORWARDEDHEADERS\_ENABLED

We will remove the manual middleware setup from the application to test this feature.  
Comment out these two lines:

```csharp
//builder.Services.Configure<ForwardedHeadersOptions>(options => 
//{
//    //TODO: Add configuration here... 
//}); 

//app.UseForwardedHeaders();
```

If you send a test request with **X-Forwarded-\*** headers, they should be ignored. This is to be expected, since the middleware is no longer active.

### Enabling the Middleware via Environment Variable

Next, let’s enable it using the **ASPNETCORE\_FORWARDEDHEADERS\_ENABLED** environment variable. The easiest way to do that is by adding it to your **launchSettings.json**:

```json
...
  "https": {
    "commandName": "Project",
    "dotnetRunMessages": true,
    "launchBrowser": true,
    "applicationUrl": "https://localhost:5001;http://localhost:5000",
    "environmentVariables": {
      "ASPNETCORE_ENVIRONMENT": "Development",
      "ASPNETCORE_FORWARDEDHEADERS_ENABLED": "true"
    }
  }
...
```

#### The FORWARDEDHEADERS\_ENABLED setting

If you’d prefer not to use environment variables, you can enable the Forwarded Headers Middleware by setting the **FORWARDEDHEADERS\_ENABLED** flag to true in your **appsettings.json**:

```json
{   
  ...   
  "FORWARDEDHEADERS_ENABLED": "true" 
}
```

#### Why Spoofing the IP No Longer Works

If you now try to spoof the remote IP address with a middleware like this:

```csharp
app.Use(async (context, next) => {
    // Spoof RemoteIpAddress      
	context.Connection.RemoteIpAddress = IPAddress.Parse("10.0.0.2");     
	
	await next(); 
});
```

…it won’t work as expected.

The Forwarded Headers Middleware is already executed before this custom middleware. When you enable it using the **ASPNETCORE\_FORWARDEDHEADERS\_ENABLED** environment variable, it’s added automatically at the very start of the pipeline by the internal **ForwardedHeadersStartupFilter**.

By the time your spoofing code runs, the middleware has already processed the request using the original **RemoteIpAddress**. So, changing it afterward has no effect. The forwarded headers will be ignored if the request appears to come from an untrusted IP (like **::1** or **127.0.0.1**), and you won’t be able to simulate a proxy this way.

### Custom Configuration with FORWARDEDHEADERS\_ENABLED

When the **ASPNETCORE\_FORWARDEDHEADERS\_ENABLED** environment variable is set to true, or the **FORWARDEDHEADERS\_ENABLED** setting is enabled in your configuration, the Forwarded Headers Middleware is automatically added at the beginning of the request pipeline.

By default, this middleware uses a built-in configuration that:

- Processes the **X-Forwarded-For** and **X-Forwarded-Proto** headers
- Clears the list of trusted proxies and networks (meaning none are trusted)

This behavior comes from the internal **ForwardedHeadersOptionsSetup** class, which you can find here: [ForwardedHeadersOptionsSetup](https://github.com/dotnet/aspnetcore/blob/main/src/DefaultBuilder/src/ForwardedHeadersOptionsSetup.cs).

#### Overriding the Default Configuration

To override this behavior, you can manually configure the options in your **Program** class:

```csharp
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders = ForwardedHeaders.All;
    options.KnownProxies.Clear();
    options.KnownProxies.Add(IPAddress.Parse("10.0.0.2"));
});
```

#### Using appsettings.json 

You can also use **appsettings.json** to configure the middleware:

```json			
"ForwardedHeaders": {
  "ForwardedHeaders": "XForwardedFor,XForwardedHost,XForwardedProto",
  "ForwardLimit": 1,
  "RequireHeaderSymmetry": true,
  "KnownProxies": [
    "::1"
  ],
  "KnownNetworks": [
    "127.0.0.1/8"
  ],
  "AllowedHosts": [
    "nestenius.se"
  ]
}
```

To apply this configuration, you still need to bind it in code using:

```csharp
builder.Services.Configure<ForwardedHeadersOptions>
            (builder.Configuration.GetSection("ForwardedHeaders"));
```

Without this line, the settings from **appsettings.json** will be ignored, and the middleware will fall back to its built-in defaults.

## Forwarded Headers in the Cloud Debugger

The [Cloud Debugger](https://github.com/tndata/CloudDebugger) is the perfect tool for exploring how forwarded headers are used in practice, especially in environments like Azure App Services or Azure Container Apps.

The [Cloud Debugger](https://github.com/tndata/CloudDebugger) is an open-source tool I created using ASP.NET Core 9. It’s designed to help developers, architects, and DevOps teams inspect, explore, and debug HTTP requests and Azure-specific behaviors in live cloud deployments.

You can deploy it directly to Azure as an [App Service](https://learn.microsoft.com/en-us/azure/app-service/overview?WT.mc_id=email&sharingId=MVP_427602) or [Container App](https://learn.microsoft.com/en-us/azure/container-apps/overview?WT.mc_id=email&sharingId=MVP_427602). Once it’s running, it provides a browser-based interface with several built-in tools to inspect HTTP headers, understand how forwarded headers are handled, and explore its middleware configurations tools.

## Source Code

You can find the source code related to this blog post on [GitHub.](https://github.com/tndataab/PublicBlogContent)

## Summary

Writing this blog post has been a deep and practical journey into the world of **proxies**, **forwarded headers,** and how **ASP.NET Core** handles these scenarios under the hood.

We started with a minimal app to explore how the middleware works in a controlled environment. Then, we stepped through configuration options, common pitfalls, and production considerations like trusted proxies and forwarded host validation. We also looked at how to test proxy scenarios locally, how logging can help when things don’t behave as expected, and what happens when you enable the middleware using the **ASPNETCORE\_FORWARDEDHEADERS\_ENABLED** environment variable.

Even though we covered a lot, there are still more advanced topics to explore! Topics like support for multiple proxy layers, deeper integration with Kubernetes ingress controllers, and the details of how PathBase actually interacts with X-Forwarded-Prefix.

If you're deploying applications behind a reverse proxy or load balancer, you can consider getting familiar with this middleware as time well spent!

And don’t forget, if you want to explore how Azure handles forwarded headers in real-world environments, check out the open-source [Cloud Debugger](https://github.com/tndata/CloudDebugger/). It’s a practical tool for testing, learning, and debugging how ASP.NET Core behaves when it’s deployed behind Azure front ends.

## Resources

- [Forwarded Headers Middleware Updates in .NET Core 3.0 preview 6](https://devblogs.microsoft.com/dotnet/forwarded-headers-middleware-updates-in-net-core-3-0-preview-6/)
- [X-Forwarded-For](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-For)
- [Asp.Net Core, reverse proxy, and X-Forwarded-* headers](https://soapfault.com/2020/02/24/asp-net-core-reverse-proxy-and-x-forwarded-headers/)
- [Configure ASP.NET Core to work with proxy servers and load balancers](https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer?view=aspnetcore-9.0&WT.mc_id=email&sharingId=MVP_427602)

## Related posts

- [Exploring the Forwarded Headers Middleware in ASP.NET Core](https://nestenius.se/net/exploring-the-forwarded-headers-middleware-in-asp-net-core/)
- [Introducing the Cloud Debugger for Azure](https://nestenius.se/azure/introducing-the-cloud-debugger-for-azure/)
- [Debugging cookie problems in ASP.NET Core](https://nestenius.se/net/debugging-cookie-problems/)
- [Improving ASP.NET Core Security By Putting Your Cookies On A Diet](https://nestenius.se/net/improving-asp-net-core-security-by-putting-your-cookies-on-a-diet/)
- [Persisting the ASP.NET Core Data Protection Key Ring in Azure Key Vault](https://nestenius.se/net/persisting-the-asp-net-core-data-protection-key-ring-in-azure-key-vault/)
- [Introducing the Coding Agent Explorer (.NET)](https://nestenius.se/ai/introducing-the-coding-agent-explorer-net/)

## Related Training Workshops

- [ASP.NET Core Fundamentals](https://tn-data.se/courses/asp-net-core-fundamentals/)
- [Web Security Fundamentals](https://tn-data.se/courses/web-security-fundamentals/)
