When implementing CORS for multiple subdomains in Nginx, many developers encounter issues with regex pattern matching for the $http_origin
variable. The common approach of using wildcard domains often fails due to subtle configuration nuances.
The core issue typically stems from three potential problems:
- The regex pattern might not account for both HTTP and HTTPS protocols
- The domain matching might be too strict about subdomain nesting
- The variable assignment might occur in the wrong context
Here's a tested solution that handles multiple subdomains:
server {
listen 443;
server_name api.example.com;
# CORS handling
set $cors "";
if ($http_origin ~* ^https?://(.*\.)?example\.com(:[0-9]+)?$) {
set $cors "true";
}
location / {
if ($cors = "true") {
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,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
}
# Handle OPTIONS requests
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_pass http://backend;
}
}
For more complex domain structures, consider these variations:
# Match specific subdomains
if ($http_origin ~* ^https?://(app|api|cdn)\.example\.com$) {
set $cors "true";
}
# Match multiple domain levels
if ($http_origin ~* ^https?://([a-z0-9-]+\.)?example\.com(:[0-9]+)?$) {
set $cors "true";
}
# Strict protocol matching
if ($http_origin ~* ^https://([a-z0-9-]+\.)*example\.com$) {
set $cors "true";
}
CORS headers not appearing: Ensure the add_header
directives are placed in the correct context. In Nginx, headers set in one block don't inherit to nested blocks.
Regex not matching: Test your pattern separately using online regex testers. Remember Nginx uses PCRE regex syntax.
SSL complications: When working with HTTPS, ensure your certificate covers all subdomains (wildcard cert or SAN certificates).
For high-traffic sites, avoid complex regex matching in the main context. Instead, consider:
map $http_origin $cors_origin {
default "";
"~^https?://([a-z0-9-]+\.)?example\.com(:[0-9]+)?$" "$http_origin";
}
server {
# ...
add_header 'Access-Control-Allow-Origin' $cors_origin;
}
Use curl to verify your CORS setup:
curl -H "Origin: http://sub.example.com" \
-I https://api.example.com/resource
Look for the Access-Control-Allow-Origin
header in the response.
The core challenge occurs when trying to match dynamic subdomains in the $http_origin
variable. Many developers struggle when their regex patterns fail to capture variations like:
https://api.example.com
http://static.example.com
https://dev.sub.example.com
The initial pattern if ($http_origin ~* (.*\.mydomain.com))
has several potential issues:
- It misses the protocol (http/https) portion
- Doesn't properly escape the dot character
- May conflict with Ansible variable substitution
Here's the corrected configuration that handles both HTTP and HTTPS subdomains:
# CORS configuration for wildcard subdomains
set $cors "";
if ($http_origin ~* ^https?://(?:[a-z0-9-]+\.)*example\.com(?:[:0-9]*)?$) {
set $cors "true";
}
location / {
# Existing proxy configuration...
if ($cors = "true") {
add_header 'Access-Control-Allow-Origin' "$http_origin";
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,X-CSRF-Token';
add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT,DELETE,PATCH';
}
}
For more complex domain structures:
# Match multiple domain patterns
if ($http_origin ~* ^https?://(?:[a-z0-9-]+\.)*(?:example\.com|api\.example\.net|dev\.example\.org)(?:[:0-9]*)?$) {
set $cors "true";
}
When using Ansible templates, ensure proper escaping:
if ($http_origin ~* ^https?://(?:[a-z0-9-]+\.)*{{ domain | regex_escape }}(?:[:0-9]*)?$) {
set $cors "true";
}
Use curl to verify CORS headers:
curl -I -H "Origin: https://test.example.com" https://your-server.com
- Missing protocol in regex pattern
- Improper escaping of dots in domain names
- Case sensitivity issues (use ~* for case-insensitive matching)
- Port numbers in origins (handled by the (?:[:0-9]*)? portion)