Handling CORS with Nginx
Last updated: 2015-08-02 :: Published: 2015-07-19 :: [ history ]You can also subscribe to the RSS or Atom feed, or follow me on Twitter.
[UPDATE 2015/08/02]
As @OtaK_ pointed out, in most cases CORS should be handled directly by the app as it should return the allowed verbs by endpoint, instead of all of them being allowed by Nginx. This config should only be used for quick development, of a prototype or PoC for example, or if you are certain that the same verbs are allowed for all the endpoints (that would be the case for the assets returned by a CDN, for instance).
[/UPDATE]
With the always wider adoption of API-driven architecture, chances are you already had to deal with cross-origin resource sharing at some point.
Whilst it is possible to deal with it from the code and you will find many packages or snippets to do so, we can remove the CORS handling from our app and let the HTTP server take care of it.
The Enable CORS website contains useful resources to this end, but when I tried to use their Nginx config for my own projects it didn't quite work as expected.
The following examples are based on the Nginx server configurations generated by Homestead, but the steps won't change much even if you are not using Laravel's dev environment.
nginx-extras
First of all, Nginx's traditional add_header
directive doesn't work with 4xx
responses. As we still want to add custom headers to them, we need to install the ngx_headers_more module to be able to use the more_set_headers
directive, which also works with 4xx
responses.
While the documentation suggests to build the Nginx source with the module, if you are on a Debian distro you can actually easily install it with the nginx-extras package:
sudo apt-get install nginx-extras
The server configuration
Here is what a typical server config of a Laravel project looks like, without the CORS bit (I am voluntarily omitting the SSL part to keep the post short, but it works exactly the same):
server {
listen 80;
server_name example-site.com;
root "/home/vagrant/projects/example-site/public";
index index.html index.htm index.php;
charset utf-8;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
access_log off;
error_log /var/log/nginx/example-site.com-error.log error;
sendfile off;
client_max_body_size 100m;
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/var/run/php5-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_intercept_errors off;
fastcgi_buffer_size 16k;
fastcgi_buffers 4 16k;
}
location ~ /\.ht {
deny all;
}
}
Now, with the CORS handling:
server {
listen 80;
server_name example-site.com;
root "/home/vagrant/projects/example-site/public";
index index.html index.htm index.php;
charset utf-8;
more_set_headers 'Access-Control-Allow-Origin: $http_origin';
more_set_headers 'Access-Control-Allow-Methods: GET, POST, OPTIONS, PUT, DELETE, HEAD';
more_set_headers 'Access-Control-Allow-Credentials: true';
more_set_headers 'Access-Control-Allow-Headers: Origin,Content-Type,Accept,Authorization';
location / {
if ($request_method = 'OPTIONS') {
more_set_headers 'Access-Control-Allow-Origin: $http_origin';
more_set_headers 'Access-Control-Allow-Methods: GET, POST, OPTIONS, PUT, DELETE, HEAD';
more_set_headers 'Access-Control-Max-Age: 1728000';
more_set_headers 'Access-Control-Allow-Credentials: true';
more_set_headers 'Access-Control-Allow-Headers: Origin,Content-Type,Accept,Authorization';
more_set_headers 'Content-Type: text/plain; charset=UTF-8';
more_set_headers 'Content-Length: 0';
return 204;
}
try_files $uri $uri/ /index.php?$query_string;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
access_log off;
error_log /var/log/nginx/example-site.com-error.log error;
sendfile off;
client_max_body_size 100m;
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/var/run/php5-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_intercept_errors off;
fastcgi_buffer_size 16k;
fastcgi_buffers 4 16k;
}
location ~ /\.ht {
deny all;
}
}
And that is pretty much it.
All you need to do now is to reload your Nginx confs:
sudo service nginx reload
Extra considerations
Note that this allows any domain to access your app, and while this is most likely enough for local development, on a production server you might want to fine-tune this configuration to allow specific domains only (Access_Control_Allow_Origin
).
More generally, all the headers' values are examples and you can modify them as you see fit.
You could also put the global and options-related snippets into separate files (in /etc/nginx/shared/
, for example) and import them with the Nginx's include
directive.
You can also subscribe to the RSS or Atom feed, or follow me on Twitter.