Eliminating Conflicting CORS Headers When Using nginx with ASP.NET Core

When an ASP.NET Core application runs behind nginx acting as a reverse proxy, duplicate CORS headers may appear in the HTTP response. Browsers reject such responses because the Access-Control-Allow-Origin header must appear at most once.

A typical browser error looks like this:

Access to XMLHttpRequest at 'http://10.0.0.5:8080/api/auth' from origin 'http://localhost:5173' 
has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header contains multiple 
values 'http://localhost:5173, *', but only one is allowed.

Inspecting the response headers reveals two separate origins:

Access-Control-Allow-Origin: http://localhost:5173
Access-Control-Allow-Origin: *

This occurs when both nginx and the ASP.NET Core pipeline attach CORS headers independently. Because the W3C CORS spceification permits only a single value for this header, the browser treats the response as invalid and blocks it.

To resolve the conflict, choose exactly one layer to manage cross-origin policy.

Option 1: Offload CORS to nginx

If the reverse proxy should own the policy, first ensure the upstream application does not emit CORS headers. In ASP.NET Core, simply omit AddCors and UseCors from the service configuration.

Then configure nginx to inject the headers and, critically, hide any headers that might leak from the backand:

upstream api_nodes {
    server 127.0.0.1:5000;
}

server {
    listen 8080;

    location / {
        proxy_pass         http://api_nodes;
        proxy_http_version 1.1;
        proxy_set_header   Upgrade           $http_upgrade;
        proxy_set_header   Connection        "upgrade";
        proxy_set_header   Host              $host;
        proxy_set_header   X-Real-IP         $remote_addr;
        proxy_set_header   X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto $scheme;

        # Remove CORS headers from the upstream response
        proxy_hide_header  Access-Control-Allow-Origin;
        proxy_hide_header  Access-Control-Allow-Methods;
        proxy_hide_header  Access-Control-Allow-Headers;
        proxy_hide_header  Access-Control-Allow-Credentials;

        # Add authoritative CORS headers
        add_header         Access-Control-Allow-Origin      $http_origin always;
        add_header         Access-Control-Allow-Methods     "GET, POST, PUT, DELETE, OPTIONS" always;
        add_header         Access-Control-Allow-Headers     "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization" always;
        add_header         Access-Control-Allow-Credentials true always;
    }
}

Using proxy_hide_header guarantees that any accidental CORS configuration in the application layer is stripped before the response reaches the client.

Option 2: Manage CORS in ASP.NET Core

Alternatively, let the application handle CORS and configure nginx to remain transparent. Remove or comment out all add_header directives related to CORS in the nginx site configuration.

Simplified nginx configuration:

server {
    listen 8080;

    location / {
        proxy_pass         http://api_nodes;
        proxy_http_version 1.1;
        proxy_set_header   Upgrade           $http_upgrade;
        proxy_set_header   Connection        "upgrade";
        proxy_set_header   Host              $host;
        proxy_set_header   X-Real-IP         $remote_addr;
        proxy_set_header   X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto $scheme;

        # Intentionally no CORS directives here
    }
}

In the ASP.NET Core project, register a named policy and apply it early in the middleware pipelien:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("GatewayPolicy", policy =>
    {
        policy.WithOrigins("http://localhost:5173", "https://app.example.com")
              .AllowAnyHeader()
              .AllowAnyMethod()
              .AllowCredentials();
    });
});

var app = builder.Build();

app.UseCors("GatewayPolicy");

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

app.Run();

Placing UseCors before UseAuthentication and UseAuthorization ensures the headers are written before the request reaches endpoints that might short-circuit the pipeline.

Tags: cors nginx ASP.NET Core reverse proxy middleware

Posted on Wed, 07 Oct 2026 16:33:40 +0000 by dr bung