Kestrel is the default web server to run ASP.NET Core application. It provides basic web server functionality while ensures the application is cross-platform. However, such application is more often than not running behind a more full-featured reverse proxy such as NGINX. Using NGINX as reverse proxy helps handling HTTP requests and adding a layer of protection (with secured HTTP or HTTPS) by working as a gateway to the server. However, such setup omits certain HTTP information when passing the requests to our application. This issue can be solved by instructing NGXINX to set headers explicitly which will be processed by the application. The official documentation shows how this can be done by using the proxy_set_header
directive:
server { listen 80; server_name example.com *.example.com; location / { ... proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }
And using the ForwardHeader
middleware at the top of the pipeline before any other middleware:
app.UseForwardedHeaders();
This ensures the HTTP context such as the scheme is passed over to our application.
However, the configuration can be different depending on different hosting strategy that we use for our application. Here we look at three common strategies:
- Hosting the application locally (localhost)
- Hosting the application behind NGINX or any reverse proxy
- Hosting the application inside docker container behind NGINX
We will look at each of the strategies below.
Hosting the Application Locally

It is not necessary to use the header forwarding since there is no proxy used. The requests go to the application through Kestrel directly.
Hosting the Application Behind NGINX

The reverse proxy (such as NGINX) has to set the above mentioned headers explicitly so that the application can get certain information such as Remote IP address
and Scheme
from the interface between NGINX and internet (client).
Hosting the Application Inside Docker Container Behind NGINX

Thing is a little tricky here if we run the application inside a Docker container because there is another layer of network between NGIXN and Docker container. If we use the same configuration as what we use for the setup without Docker, the header forwarding is working as if it is disabled.
This is because header forwarding uses loopback IP address as the default known networks when obtaining the headers. The loopback is the localhost or 127.0.0.1
. However, the IP address inside a Docker container is ::ffff:172.17.0.2
which is not known by the application, hence the headers are not accepted or processed. To solve this, we have to clear the known networks in the configuration:
services.Configure<ForwardedHeadersOptions>(options => { options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; options.ForwardLimit = 2; options.KnownNetworks.Clear(); options.KnownProxies.Clear(); });
This ensure the default networks (and proxies) are cleared so the application will then accept headers from any IP address. The comparison below shows the differences with and without clearing the known network values:
Properties | Without clearing known networks | With clearing known networks |
---|---|---|
Remote IP address | ::ffff:172.17.0.1 | <client IP address, hidden> |
Remote port number: | 48500 | 0 |
Local IP address | ::ffff:172.17.0.1 | ::ffff:172.17.0.1 |
Local port number | 80 | 80 |
Scheme | http | https |
Host | coffee-shop-talk-stg.tengweisong.com | coffee-shop-talk-stg.tengweisong.com |
As we can see, just by clearing the known networks, headers are passed successfully to the application. The remote IP address represents the IP address the HTTP requests originates from, and the scheme picks up the https
correctly because the connection between client and NGINX is protected by SSL certificate while the rest of the connection is http
only (between NGINX and Docker container as well as between Docker container and application).