Deep Analysis and Best Practices for CORS Configuration in Nginx Proxy Server

Dec 01, 2025 · Programming · 12 views · 7.8

Keywords: Nginx | CORS | Reverse Proxy | Cross-Origin Resource Sharing | Preflight Request

Abstract: This article provides an in-depth exploration of Cross-Origin Resource Sharing (CORS) configuration principles and common issues in Nginx reverse proxy environments. Through analysis of practical configuration cases, it explains the CORS preflight request mechanism, Nginx add_header directive inheritance characteristics, and two effective solutions for resolving 405 errors. The article also combines best practices for proxy response header handling, offering complete configuration examples and performance optimization recommendations to help developers build secure and reliable cross-origin API services.

Fundamental Principles and Problem Analysis of CORS Configuration

Cross-Origin Resource Sharing (CORS) is a critical technology in modern web application development, allowing browsers to make cross-origin requests to servers in different domains. In Nginx reverse proxy environments, proper configuration of CORS headers is essential for ensuring normal access to API services.

In typical configuration scenarios, developers often encounter preflight request failures. Preflight requests are OPTIONS requests initiated by browsers before sending actual requests, used to confirm whether the server allows cross-origin access. When the server fails to properly respond to preflight requests, browsers block subsequent actual requests, resulting in 405 Method Not Allowed errors.

Inheritance Characteristics of Nginx add_header Directive

The Nginx add_header directive has specific inheritance rules: within the same configuration block, if multiple add_header directives exist, only the last header with the same name will be preserved. More importantly, when using add_header in child blocks (such as if blocks), headers from parent blocks are not automatically inherited into child blocks.

Consider the following configuration example:

location / {
    add_header 'Access-Control-Allow-Origin' 'http://api.localhost';
    add_header 'Access-Control-Allow-Credentials' 'true';
    
    if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Max-Age' 1728000;
        return 204;
    }
}

In this configuration, when the browser sends an OPTIONS request, although the if block is executed, the CORS headers from the parent location / block are not included in the response. This explains why preflight requests fail, as the browser cannot receive necessary headers like Access-Control-Allow-Origin.

Solution One: Duplicate CORS Headers in if Block

The most direct solution is to explicitly duplicate all necessary CORS headers within the if block:

server {
    listen 80;
    server_name api.localhost;

    location / {
        add_header 'Access-Control-Allow-Origin' 'http://api.localhost';
        add_header 'Access-Control-Allow-Credentials' 'true';
        add_header 'Access-Control-Allow-Headers' 'Authorization,Accept,Origin,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range';
        add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT,DELETE,PATCH';

        if ($request_method = 'OPTIONS') {
            add_header 'Access-Control-Allow-Origin' 'http://api.localhost';
            add_header 'Access-Control-Allow-Credentials' 'true';
            add_header 'Access-Control-Allow-Headers' 'Authorization,Accept,Origin,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range';
            add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT,DELETE,PATCH';
            add_header 'Access-Control-Max-Age' 1728000;
            add_header 'Content-Type' 'text/plain charset=UTF-8';
            add_header 'Content-Length' 0;
            return 204;
        }

        proxy_redirect off;
        proxy_set_header host $host;
        proxy_set_header X-real-ip $remote_addr;
        proxy_set_header X-forward-for $proxy_add_x_forwarded_for;
        proxy_pass http://127.0.0.1:3000;
    }
}

This approach ensures that both OPTIONS requests and regular requests receive complete CORS headers. Although the configuration is somewhat redundant, it is very effective for scenarios requiring fine-grained control over different request types.

Solution Two: Move CORS Headers to Server Block

Another more concise solution is to elevate generic CORS headers to the server block level:

server {
    listen 80;
    server_name api.localhost;

    add_header 'Access-Control-Allow-Origin' 'http://api.localhost';
    add_header 'Access-Control-Allow-Credentials' 'true';
    add_header 'Access-Control-Allow-Headers' 'Authorization,Accept,Origin,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range';
    add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT,DELETE,PATCH';

    location / {
        if ($request_method = 'OPTIONS') {
            add_header 'Access-Control-Max-Age' 1728000;
            add_header 'Content-Type' 'text/plain charset=UTF-8';
            add_header 'Content-Length' 0;
            return 204;
        }

        proxy_redirect off;
        proxy_set_header host $host;
        proxy_set_header X-real-ip $remote_addr;
        proxy_set_header X-forward-for $proxy_add_x_forwarded_for;
        proxy_pass http://127.0.0.1:3000;
    }
}

This method leverages Nginx configuration inheritance characteristics, where all requests entering this server block automatically include basic CORS headers. For OPTIONS requests, only additional preflight-specific headers need to be added.

Proxy Response Header Handling Strategies

In reverse proxy scenarios, it's also important to note that backend servers may have already set CORS headers. If both the backend server and Nginx set the same headers, duplicate header issues may occur.

The situation mentioned in the reference article demonstrates how to handle this:

map $http_origin $cors_header {
    default "";
    "~^https?://[^/]+\.mydomain\.com(:[0-9]+)?$" $http_origin;
}

server {
    location / {
        proxy_pass https://myrestserver.com/api;
        add_header Access-Control-Allow-Origin $cors_header;
        add_header Access-Control-Allow-Credentials true;
    }
}

This configuration uses the map directive to dynamically generate the Access-Control-Allow-Origin header and overrides the same header sent by the backend server. This approach ensures consistency in CORS policies and avoids duplicate header issues.

Configuration Templates and Modular Management

For large projects or multiple API endpoints, using configuration templates to manage CORS headers is recommended:

# conf.d/corsheaders.conf
add_header 'Access-Control-Allow-Origin' 'http://api.localhost';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Headers' 'Authorization,Accept,Origin,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range';
add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT,DELETE,PATCH';

# api-server.conf
server {
    listen 80;
    server_name api.localhost;

    location /api {
        include conf.d/corsheaders.conf;
        
        if ($request_method = 'OPTIONS') {
            include conf.d/corsheaders.conf;
            add_header 'Access-Control-Max-Age' 1728000;
            add_header 'Content-Type' 'text/plain charset=UTF-8';
            add_header 'Content-Length' 0;
            return 204;
        }

        proxy_pass http://127.0.0.1:3000;
    }
}

This method improves configuration maintainability, making it easier to uniformly manage and update CORS policies.

Security Considerations and Best Practices

When configuring CORS, special attention should be paid to security:

By deeply understanding Nginx configuration characteristics and CORS working principles, developers can build both secure and efficient cross-origin API services. Proper CORS configuration not only solves cross-origin access issues but also provides better user experience and stronger security guarantees for web applications.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.