Skip to main content

Getting Correct Scheme, Host, Port Behind a Proxy

When running an API behind a reverse proxy, such as NGINX, the service will not, by default, see the scheme and port of the incoming call. By default, the API service will see the scheme and port of the direct call to it, which will likely be http and some internal port (5000 maybe).

This is often because the reverse proxy is terminating SSL, and forwarding an http requestrequests to your API.

The problem with thisthis, is,is that your API has no way to correctly compose Urls for redirection, because it doesn't know the scheme and port.

To ensure that your API can see the correct scheme, host, and port, you need to do the following things.

NGINX Config

In the server block of your reverse proxy, verify you have the following three directives:

# These forward the scheme type (http or https), hostname, and listening port.
# These are used by the service, when generating redirect urls.
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Port $server_port;

Startup.cs

The dotnet service doesn't accept the forwarded headers, by default. So, we need to configure it to accept them.
This is done in the Russian doll logic of your Startup:Configure() routine.

Locate the Configure() of your Startup.cs, and find the UseRouting(); line.
Add the following block after UseRouting():

// Accept the forwarded headers (scheme, host, port) from NGINX.
// We do this, because we are running behind a reverse proxy, and don't directly know the scheme and port.
// So, we have NGINX forward these to us.
// And, this statement accepts them.
// See this page for details: https://wiki.galaxydump.com/link/476
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
    ForwardedHeaders = ForwardedHeaders.XForwardedProto | 
                       ForwardedHeaders.XForwardedHost  | 
                       ForwardedHeaders.XForwardedFor,
    // Important: trust only your proxy
    // options.KnownProxies.Add(IPAddress.Parse("127.0.0.1"));
    // or
    // options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("10.0.0.0"), 8));
});

Usage

Once you have NGINX forwarding the correct scheme, host, and port, and your Startup.cs is accepting it, you can access them like this:

HttpRequest request = httpContext!.Request;

// Retrieve the scheme that was forwarded by NGINX...
// This is set by the following directive in NGINX:
//  proxy_set_header X-Forwarded-Proto $scheme;
var scheme = request.Scheme;
// Retrieve the host that was forwarded by NGINX...
// This is set by the following directive in NGINX:
//  proxy_set_header Host $host;
var hostname = request.Host.Host;
// Retrieve the port that was forwarded by NGINX...
// This is set by the following directive in NGINX:
//  proxy_set_header X-Forwarded-Port $server_port;
var port = request.Host.Port;

A further example of usage, is to correctly compose a base URI from what NGINX told us about the request.

The following will retrieve the scheme, host, and port.
And, it will compose a base URI that matches what NGINX saw from the original call.

HttpRequest request = httpContext!.Request;

// Retrieve the scheme that was forwarded by NGINX...
// This is set by the following directive in NGINX:
//  proxy_set_header X-Forwarded-Proto $scheme;
var scheme = request.Scheme;
// Retrieve the host that was forwarded by NGINX...
// This is set by the following directive in NGINX:
//  proxy_set_header Host $host;
var hostname = request.Host.Host;
// Retrieve the port that was forwarded by NGINX...
// This is set by the following directive in NGINX:
//  proxy_set_header X-Forwarded-Port $server_port;
var port = request.Host.Port;

// In case the port was not set, we will accept defaults, based on scheme...
if (request.Host.Port.HasValue)
    port = request.Host.Port.Value;
else
{
    if (scheme == "http")
        port = 80;
    else if (scheme == "https")
        port = 443;
}

bool nondefaultport = false;
if(scheme == "http" && port != 80)
    nondefaultport = true;
if(scheme == "https" && port != 443)
    nondefaultport = true;

// Compose the base url string...
string baseUri = scheme + "://" + hostname;
// If the port is not default, add it explicitly...
if(nondefaultport)
    baseUri = baseUri + ":" + port.ToString();

// Create the uri service from the above base url string...
var svc = new UriService(baseUri);

The above can be passed to a URIService instance, so it can help compose redirects and pagination.

// Create the uri service from the above base url string...
var svc = new UriService(baseUri);