Skip to content

Instantly share code, notes, and snippets.

@alexjs
Forked from michiel/cors-nginx.conf
Created November 28, 2012 22:42
Show Gist options
  • Save alexjs/4165271 to your computer and use it in GitHub Desktop.
Save alexjs/4165271 to your computer and use it in GitHub Desktop.
Slightly tighter CORS config for nginx
#
# Slightly tighter CORS config for nginx
#
# A modification of https://gist.github.com/1064640/ to include a white-list of URLs
#
# Despite the W3C guidance suggesting that a list of origins can be passed as part of
# Access-Control-Allow-Origin headers, several browsers (well, at least Firefox)
# don't seem to play nicely with this.
#
# To avoid the use of 'Access-Control-Allow-Origin: *', use a simple-ish whitelisting
# method to control access instead.
#
# NB: This relies on the use of the 'Origin' HTTP Header.
location / {
if ($http_origin ~* (whitelist\.address\.one|whitelist\.address\.two)) {
set $cors "true";
}
# Nginx doesn't support nested If statements. This is where things get slightly nasty.
# Determine the HTTP request method used
if ($request_method = 'OPTIONS') {
set $cors "${cors}options";
}
if ($request_method = 'GET') {
set $cors "${cors}get";
}
if ($request_method = 'POST') {
set $cors "${cors}post";
}
if ($cors = "true") {
# Catch all incase there's a request method we're not dealing with properly
add_header 'Access-Control-Allow-Origin' "$http_origin";
}
if ($cors = "trueget") {
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-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
}
if ($cors = "trueoptions") {
add_header 'Access-Control-Allow-Origin' "$http_origin";
#
# Om nom nom cookies
#
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
#
# Custom headers and headers various browsers *should* be OK with but aren't
#
add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
#
# Tell client that this pre-flight info is valid for 20 days
#
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}
if ($cors = "truepost") {
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-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
}
}
@Ry4an
Copy link

Ry4an commented Aug 9, 2013

Thanks for this. I think you want a $ at the end of the regex on line 18. Without it my site http://whitelist.address.one.evil.com will pass and show up in the Access-Control-Allow-Origin header.

@jmenchacavr
Copy link

$http_origin is empty on my system. Is there some mechanism needed to have this available?

@kaitsche
Copy link

curl -I -X OPTIONS -H "Origin: http://www.example.com" http://www.yourdomain.com

@spyrospph
Copy link

Hi,

I have observed when I use an if statement within a location then nginx returns a 404.

So the below does not work:

if ($cors = "true") {
add_header 'Access-Control-Allow-Origin' "$http_origin";
}

try_files $uri $uri/ /index.php?$args;

causes nginx to not reach the try_files directive and return a 404.

Anyone else with this issue?

@mox601
Copy link

mox601 commented Mar 17, 2015

@spyrospph I have the same problem, and it's also documented at IfIsEvil.
imho, if there are no ways to add cors headers without using ifs, don't use nginx.

@thoughtless
Copy link

@laijiang19
Copy link

What does 'X-Mx-ReqToken' header actually do? I see it in all CORS tutorials for nginx but can't find any info otherwise.

@ccrice
Copy link

ccrice commented Jan 11, 2017

This is very helpful

@bfricka
Copy link

bfricka commented Jan 4, 2018

According to nginx, if shouldn't be used in a location block like this.

@macedd
Copy link

macedd commented Feb 13, 2018

Correct ^
All this ifs are different levels and should not work together according to add_header usage docs.

@michalzvolanek
Copy link

Very well, thanks

Copy link

ghost commented Oct 30, 2018

Thanks for this. I think you want a $ at the end of the regex on line 18. Without it my site http://whitelist.address.one.evil.com will pass and show up in the Access-Control-Allow-Origin header.

+1 came here to say this

@cnjax
Copy link

cnjax commented Jul 24, 2019

it is great,thank you ,it works.

@mrbrazzi
Copy link

Because the use of if is evil inside location like @mox601 and @bfricka said before, this was my solution::

location / {
    try_files $uri $uri/ /index.php$is_args$args;
}

location ~ \.php$ {
    try_files $uri /index.php =404;
    fastcgi_pass php-upstream;
    fastcgi_index index.php;
    fastcgi_buffers 16 16k;
    fastcgi_buffer_size 32k;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    #fixes timeouts
    fastcgi_read_timeout 600;
    include fastcgi_params;

    if ($request_method = "OPTIONS") {
        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Allow-Credentials true;
        add_header Access-Control-Allow-Methods 'DELETE,GET,OPTIONS,POST,PUT';
        add_header Access-Control-Allow-Headers 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With,X-Token-Auth,X-Mx-ReqToken,X-Requested-With';
        add_header 'Access-Control-Max-Age' 1728000;
        add_header 'Content-Type' 'text/plain charset=UTF-8';
        add_header 'Content-Length' 0;

        return 204;
    }

    add_header Access-Control-Allow-Origin *;
    add_header Access-Control-Allow-Credentials true;
    add_header Access-Control-Allow-Methods 'DELETE,GET,OPTIONS,POST,PUT';
    add_header Access-Control-Allow-Headers 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With,X-Token-Auth,X-Mx-ReqToken,X-Requested-With';
}

@mbrowne
Copy link

mbrowne commented Jun 10, 2020

I couldn't even get if to work at all without a return at the end, which didn't work for me because I needed to do proxy_pass. So this is the solution I ended up with, using map:

    # If the request is from an allowed domain or subdomain of that domain,
    # we set the values of the CORS headers to be ued below
    map $http_origin $use_cors {
      '~*^https?://(.+\.)*(whitelist\.address\.one|whitelist\.address\.two)' 'true';
    }
    map $use_cors $allow_origin {
      'true' $http_origin;
    }
    map $use_cors $allow_methods {
      'true' 'GET, OPTIONS, POST';
    }
    map $use_cors $allow_headers {
      'true' 'User-Agent,Keep-Alive,Content-Type,Pragma,Cache-Control,Upgrade-Insecure-Requests';
    }

    server {
      ...

      location / {
        # This prevents OPTIONS requests from failing
        if ($request_method = OPTIONS) {
          return 204;
        }

        # Allow ajax requests from other domains and subdomains
        add_header Access-Control-Allow-Origin $allow_origin;
        add_header Access-Control-Allow-Methods $allow_methods;
        add_header Access-Control-Allow-Headers $allow_headers;

        # Your other config here...
      }
    }

@programarivm
Copy link

If you're using PHP this basic set up may help.

default.conf

server {
    listen 443 ssl;
    server_name             pgn-chess-data.local;
    ssl_certificate         /etc/nginx/ssl/pgn-chess-data.local.crt;
    ssl_certificate_key     /etc/nginx/ssl/pgn-chess-data.local.key;
    ssl_ciphers             EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH;
    ssl_protocols           TLSv1.1 TLSv1.2;

    root /usr/share/nginx/pgn-chess-data/public;

    client_max_body_size 20M;

    location ~ ^/api/ {
        try_files $uri /index.php$is_args$args;
    }

    location ~ ^/.+\.php(/|$) {
        fastcgi_pass php_fpm:9000;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
}

public/index.php

<?php

require realpath(dirname(__FILE__)) .'/../src/bootstrap.php';

header("Access-Control-Allow-Origin: *");
header('Access-Control-Allow-Methods: GET, POST');
header("Access-Control-Allow-Headers: DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range");

switch (true) {
    case '/api/query' === $_SERVER['REQUEST_URI'] && $_SERVER['REQUEST_METHOD'] === 'POST':
        require APP_PATH . '/src/Api/Query.php';
        exit;
    case $_SERVER['REQUEST_METHOD'] === 'OPTIONS':
        http_response_code(204);
        exit;
    default:
        http_response_code(404);
        exit;
}

@tonykor
Copy link

tonykor commented Dec 6, 2020

Hello, I have fixed try_files problem next way

server {
    server_name api.domain.com;
    set $cors "https://domain.com";
    if ($http_origin ~* (/|\.)domain\.(com|dev)$) {
        set $cors $http_origin;
    }
    add_header 'Access-Control-Allow-Origin' '$cors' always;
    ...
    ...
    ...
    location / {
       try_files $uri $uri/ /index.php$is_args$args;
    }
}

@BlackTurtle123
Copy link

BlackTurtle123 commented Apr 13, 2021

For me something like this broke my preflight check for cors and therefor failing cors for some reason...

` gzip off;
proxy_set_header X-Forwarded-Ssl on;
client_max_body_size 50M;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $http_host;
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 $scheme;
proxy_set_header X-Frame-Options SAMEORIGIN;
proxy_pass http://test.app_backend;

  set $cors "";

  if ($http_origin ~* (.*\.test.app|https://waves.exchange|.*\.test2.app|.*\.wallet.com)) {
    set $cors "true";
  }
  if ($cors = "true") {
  add_header 'Access-Control-Allow-Origin' "$http_origin" always;
  add_header 'Access-Control-Allow-Credentials' 'true' always;
  add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE' always;
  add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,signature,timestamp' always;
  add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always;
  }
  if ($request_method = 'OPTIONS') {
    return 204;
  }

`

@luohoufu
Copy link

work for me

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment