In June 2025, Microsoft released patch version 8.0.17 and 9.0.6 of .NET, which annoyingly contained a behaviourally breaking change in how HTTP header forwarding is enabled. Finding out about the change was pretty hard, as it wasn’t listed in the release notes but instead only mentioned in a GitHub issue off to the side.
Since this is a runtime change, it means that if you’re not being specific in the .NET framework version you’re pulling into your Docker image then just the act of deploying your existing application – having modified no source code or configuration at all – can result in the following behaviour:
- An error in logs at startup, “Failed to determine the https post for redirect”
- Sign-in errors for your users when using OpenID Connect or SAML sign-in, related to invalid redirect URLs being requested
- URLs that your application generates (for example as redirects, or entity URLs in APIs) via ASP.NET utilities referring to HTTP rather than HTTPS
This only manifests in the following setup:
- You’re using a proxy or load balancer in front of your application that is responsible for terminating TLS connections (like AWS Application Load Balancer)
- You’re not using IIS to host the application
Cause and fix
.NET now validates that the incoming connection is on a whitelist of peers that are allowed to send X-Forwarded* headers to the application, when previously the absence of such a whitelist was treated as ‘accept any peer’.
The following code:
var forwardingOpts = new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedProto
};
app.UseForwardedHeaders(forwardingOpts);
behaves differently in .NET 8.0.17 and previous versions.
- Before 8.0.17, the above meant ‘trust the X-ForwardedProto header from any source’
- Since 8.0.17, the above meant ‘trust the X-ForwardedProto header for entries in the KnownNetworks/KnownProxies lists on the configuration only’
Since the previous behaviour was default-permissive, most applications (incorrectly) didn’t specify the set of known proxies and networks that should be trusted to supply valid X-Forwarded* headers.
The fix then is to specify one or other of KnownProxies or KnownNetworks with information describing the load balancer that’s actually terminating the client connection. For example, if we want to trust any LB within our VPC’s network and we’re using a CIDR of 10.20.0.0/16 for our VPC, we might configure KnownNetworks as:
forwardingOpts.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("10.20.0.0"), 16));
Though – typically you want to be much tighter here, and potentially specify the actual IP addresses that the LB might present as rather than a broad IP range.
Cause (detailed)
When you’re using a load balancer (LB for short from here on out), clients aren’t hitting your application directly. Instead, they’re hitting the LB, which proxies or forwards the request to your application. The LB will often strip some headers from the original request before forwarding it, so the request your application sees is really only part of the actual picture.
For example, your application won’t know whether the client’s talking via HTTP or HTTPS, since it can’t see information about the actual request that was sent to the LB. From its point of view, the LB is the client, and it’s connecting over whatever port you’re exposing – which if you’re using Docker will likely be some non-TLS random port.
The LB can fill the picture in for your application by forwarding some of the information about the original client connection to the application in a special set of headers:
- X-ForwardedFor – the original IP address of the connecting client
- X-ForwardedHost – the host name that the client is connecting to
- X-ForwardedProto – the protocol (HTTP or HTTPS) that the client used to connect to the LB
However this comes with security considerations – a permissive LB that just passes on these headers blindly would mean a client could connect with a fake set of X-Forwarded* headers, allowing a variety of attacks.
To mitigate this, the LB and the application need to cooperate a little:
- The LB typically has to be configured to trust or reject incoming X-Forwarded* headers
- The application has to be configured to accept or reject some or all X-ForwardedFor headers
Prior to .NET 8.0.17, the ForwardedHeadersMiddleware assumed that unless told otherwise it should accept X-Forwarded* headers from any source when told which headers to accept.
Since 8.0.17 the behaviour changed, so that if not told otherwise the middleware assumes that no source is permitted to send the headers.
The fix is to tell the ForwardedHeadersMiddleware exacrtly which IP addresses to expect traffic from where we’ll be accepting the X-Forwarded* headers. We do this by adding to the KnownNetworks and KnownProxies collections.










