How to Configure Wildcard Domain CORS in Nginx: Access-Control-Allow-Origin *.mydomain.com


9 views

While the CORS specification technically allows wildcards in Access-Control-Allow-Origin headers, there's an important distinction to understand. A literal * works fine, but partial wildcards like *.mydomain.com aren't valid per the W3C specification. Here's what actually happens when you try:


# This WON'T work as expected
add_header Access-Control-Allow-Origin "*.example.com";

For nginx configurations needing to allow multiple subdomains, we have several robust approaches:

1. Regex-Based Matching

The most flexible solution uses nginx's map directive with regex pattern matching:


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

server {
    ...
    add_header Access-Control-Allow-Origin $cors_origin;
    add_header Access-Control-Allow-Credentials true;
    ...
}

2. Multiple Origin Handling

For known subdomains, explicitly list them with conditionals:


set $cors "";
if ($http_origin ~* (https?://.*\.mydomain\.com$)) {
    set $cors $http_origin;
}

add_header Access-Control-Allow-Origin $cors;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range";

When implementing CORS with wildcard-like behavior:

  • Always combine with Vary: Origin header to prevent cache poisoning
  • Never use * with Access-Control-Allow-Credentials: true
  • Consider rate limiting for OPTIONS requests to prevent CORS-based DDoS

Verify your setup with curl commands:


# Should return the origin if matched
curl -H "Origin: https://app.mydomain.com" -I https://api.mydomain.com/resource

# Should not return ACAO header for unmatched domains
curl -H "Origin: https://malicious.com" -I https://api.mydomain.com/resource

For high-traffic applications:


# Cache preflight responses for 20 days
add_header Access-Control-Max-Age 1728000;

# Compile regex patterns once at startup
map $http_origin $cors_origin {
    default "";
    "~^https?://(www\.|app\.|api\.)?mydomain\.com$" $http_origin;
    "~^https?://([a-z0-9-]+-staging\.)?mydomain\.com$" $http_origin;
}

When implementing CORS in Nginx, many developers encounter limitations with wildcard domain patterns. While browsers support Access-Control-Allow-Origin: *.mydomain.com in responses, Nginx requires specific configuration to achieve this functionality properly.

The most robust approach uses regular expressions to match subdomains dynamically:

server {
    listen 80;
    server_name api.mydomain.com;

    # Dynamic CORS for subdomains
    if ($http_origin ~* ^https?://([a-z0-9-]+\.)?mydomain\.com$) {
        add_header 'Access-Control-Allow-Origin' "$http_origin";
        add_header 'Access-Control-Allow-Credentials' 'true';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
    }

    location / {
        # Your normal configuration
        proxy_pass http://backend;
    }
}

1. The regex pattern ^https?://([a-z0-9-]+\.)?mydomain\.com$ matches:

  • http://app.mydomain.com
  • https://dev.mydomain.com
  • http://mydomain.com

2. For production environments, consider pre-compiling the regex for better performance:

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

server {
    add_header 'Access-Control-Allow-Origin' $cors_origin;
    # Other headers...
}

Use curl to verify headers:

curl -I -H "Origin: http://test.mydomain.com" https://api.mydomain.com

Expected response should include:

Access-Control-Allow-Origin: http://test.mydomain.com
Access-Control-Allow-Credentials: true

For invalid origins, the header should be absent or show your default CORS policy.

1. Always combine with proper authentication

2. Limit allowed HTTP methods

3. Set appropriate Cache-Control for CORS responses

4. Consider adding Vary: Origin header

add_header 'Vary' 'Origin';