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
*
withAccess-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';