16

My nginx version: nginx/1.4.6

I have an issue enabling CORS for multiple subdomains. I checked https://gist.github.com/algal/5480916 and http://rustyrazorblade.com/post/2013/2013-10-31-cors-with-wildcard-domains-and-nginx/ but both solutions doesn't work for me.

It looks like the regex

if ($http_origin ~* (.*\.mydomain.com)) { set $cors "true"; } 

is not matching and $cors is not set to "true" and therefor add_header 'Access-Control-Allow-Origin' "$http_origin" won't be executed.

I also tried with regex

$http_origin ~* (https?://.*.mydomain.com)

or

$http_origin ~* https?://.*.mydomain.com

But in either case the regex doesn't match and $cors will never set to "true".

What am I missing?

My nginx configuration - domain name in curly braces (is getting replaced by Ansible):

upstream varnish { server localhost:80; } server { listen 443 default; server_name {{vhost}}; ssl on; ssl_certificate /etc/ssl/certs/ssl.{{domain}}.crt; ssl_certificate_key /etc/ssl/private/{{domain}}.key; ssl_session_timeout 5m; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; #ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP; #ssl_prefer_server_ciphers on; # workaround remote exploit. Fixed in 1.5.0, 1.4.1 # # http://mailman.nginx.org/pipermail/nginx-announce/2013/000112.html if ($http_transfer_encoding ~* chunked) { return 444; } 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 https; proxy_set_header Host $http_host; proxy_redirect off; # CORS set $cors ""; if ($http_origin ~* (.*\.{{domain}})) { set $cors "true"; } location / { # Set the max size for file uploads (/admin, /webmail) client_max_body_size 10G; proxy_pass http://varnish; 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'; } if ($request_method = OPTIONS) { return 204; } } location = /50x.html { root html; } } 
2
  • could you show us how the config looks like when Ansible replaced the variables? Commented Mar 29, 2019 at 7:32
  • Did you test it with a POST instead of a GET? See my answer below! Commented Aug 11, 2020 at 15:03

5 Answers 5

28

There are some unexpected things that occur when using if inside location blocks in NGINX. It's not recommended. Here is a solution that uses map. https://www.nginx.com/resources/wiki/start/topics/depth/ifisevil/ and https://agentzh.blogspot.com/2011/03/how-nginx-location-if-works.html

This setup allows me to make requests to any subdomain and any port on my-domain.com and localhost (for development).

map $http_origin $allow_origin { ~^https?://(.*\.)?my-domain.com(:\d+)?$ $http_origin; ~^https?://(.*\.)?localhost(:\d+)?$ $http_origin; default ""; } server { listen 80 default_server; server_name _; add_header 'Access-Control-Allow-Origin' $allow_origin; add_header Vary Origin; # ... } 

http://nginx.org/en/docs/http/ngx_http_map_module.html

Note:

the server should also include Origin in the Vary response header to indicate to clients that server responses will differ based on the value of the Origin request header.

https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#access-control-allow-origin

2
  • Any idea how one would implement this with more_set_headers? Commented Feb 15, 2021 at 21:33
  • Did soemthing with nginx change? I just tried this exact map block in my own nginx config file and it crashes the server with nginx: [emerg] invalid number of the map parameters in /etc/nginx/nginx.conf:44 Commented Dec 31, 2024 at 19:00
6

You can get around the limitation of only one subdomain by using this clever workaround that will allow all subdomains:

server { root /path/to/your/stuff; index index.html index.htm; set $cors ""; if ($http_origin ~* (.*\.yoursweetdomain.com)) { set $cors "true"; } server_name yoursweetdomain.com; location / { if ($cors = "true") { add_header 'Access-Control-Allow-Origin' "$http_origin"; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, DELETE, PUT'; add_header 'Access-Control-Allow-Credentials' 'true'; add_header 'Access-Control-Allow-Headers' 'User-Agent,Keep-Alive,Content-Type'; } if ($request_method = OPTIONS) { return 204; } } } 

Credit: http://rustyrazorblade.com/post/2013/2013-10-31-cors-with-wildcard-domains-and-nginx/

3
  • 1
    did you read my original post/question? In my first phrase I mentioned that this link/source doesn't work for me. Commented Apr 1, 2019 at 8:00
  • 3
    Nginx if is evil. Commented Jul 24, 2019 at 9:37
  • After 48hours of stalling because of a CORs issue. this worked for me! Thanks. Commented Feb 19, 2021 at 4:21
1

its been a year but, here is the solution that worked for me.

location / { proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # Nginx doesn't support nested If statements, so we # concatenate compound conditions on the $cors variable # and process later # If request comes from allowed subdomain # (*.mckinsey.com) then we enable CORS if ($http_origin ~* (https?://.*\.mckinsey\.com(:[0-9]+)?$)) { set $cors "1"; } # OPTIONS indicates a CORS pre-flight request if ($request_method = 'OPTIONS') { set $cors "${cors}o"; } # Append CORS headers to any request from # allowed CORS domain, except OPTIONS if ($cors = "1") { more_set_headers 'Access-Control-Allow-Origin: $http_origin'; more_set_headers 'Access-Control-Allow-Credentials: true'; proxy_pass http://serverIP:serverPort; } # OPTIONS (pre-flight) request from allowed # CORS domain. return response directly if ($cors = "1o") { more_set_headers 'Access-Control-Allow-Origin: $http_origin'; more_set_headers 'Access-Control-Allow-Methods: GET, POST, OPTIONS, PUT, DELETE'; more_set_headers 'Access-Control-Allow-Credentials: true'; more_set_headers 'Access-Control-Allow-Headers: Origin,Content-Type,Accept'; add_header Content-Length 0; add_header Content-Type text/plain; return 204; } # Requests from non-allowed CORS domains proxy_pass http://serverIP:serverPort; } 

Source: https://gist.github.com/bramswenson/51f0721dec22b9b258aea48b59e9a32c

0

Try moving the check for $http_origin into your location block.

You can see the same in the first example link you gave.

The variable is probably first filled when the location block is called.

1
  • moving the check for $http_origin into your location block doesn't change anything Commented Mar 28, 2019 at 16:05
0

$http_origin contains the value of the "origin" field in the request header.

The reason why you might have the impression that it does not work is that you tested it with a request where the "origin" header field is empty. Example: Browsers do not set the origin field on GET requests, only on POST and maybe more...

For exact info, see https://stackoverflow.com/questions/42239643/when-do-browsers-send-the-origin-header-when-do-browsers-set-the-origin-to-null

So, the code above works perfectly OK because your GET requests do not need the CORS fields in the response header. GET works without those fields!

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.