search by tags

for the user

adventures into the land of the command line

adding CORS for flask, nginx and ajax

Ok so this is the setup:

An ajax request from the browser at sends a DELETE request to an api on a server at is a flask webapp running in gunicorn, with an nginx reverse proxy. is a flask webapp also running in gunicorn, also with an nginx reverse proxy. and are on separate servers.

The purpose of the ajax request in this example is to delete a transaction on a page, and then update the UI to remove the div that the deleted transaction was located. It looks like this:

$( ".some-button" ).click(function( event ) {
                    var payload = {'transaction_id':'some_id'};
                    var that = $(this);

                        type : "DELETE",
                        crossDomain: true,
                        dataType: 'json',
                        url : completeUrl,
                        data: JSON.stringify(payload, null, '\t'),
                        contentType: 'application/json'
                    }).done( function(result) {
                            "height" : "0",
                            "opacity" : "0",
                            }, {
                            duration: 400,
                            complete : function() {

Ajax will by default send a ‘preflight’ HTTP OPTION request to, asking what it is allowed to do, as it is at a different domain.

You need to set up the server at to respond to this preflight request with what you want to allow to do.

I do this in my nginx config:

server {
    location / {
    if ($request_method = 'OPTIONS') {
            add_header          'Access-Control-Allow-Origin' '';
            add_header          'Access-Control-Allow-Methods' 'DELETE';
            add_header          'Access-Control-Allow-Headers' 'Content-Type';
            add_header          'Access-Control-Allow-Credentials' 'true';
            add_header          'Access-Control-Max-Age' 60;
            add_header          'Content-Type' 'text/plain charset=UTF-8';
            add_header          'Content-Length' 0;
            return 204;

Nginx will return these access control headers to You gotta make sure you have Access-Control-Allow-Origin, it’s super important. will see response headers that include these:


Cool, now knows what it’s allowed to do, and will continue with the actual DELETE request.’s nginx will forward this request to its flask app. Flask will do whatever and then return a response. The response will also need to include the OPTIONS directive and these headers:

@app.route('/v1/blah' , methods=['GET','POST','PATCH','DELETE','OPTIONS'])
response['token'] = token
js = json.dumps(response)
resp = Response(js, status=201, mimetype='application/json')
resp.headers['Access-Control-Allow-Origin'] =
resp.headers['Access-Control-Allow-Methods'] = 'DELETE'
resp.headers['Access-Control-Allow-Headers'] = 'Content-Type'
resp.headers['Access-Control-Allow-Credentials'] = 'true'
resp.headers['Access-Control-Max-Age'] = 60
return resp

It’s really important that the Access-Control-Allow-Origin response header contains the domain for; in other words, the domain you want to allow access to It can also be ’*’, which is super unsafe, or match exactly. You can’t wildcard it like * That won’t work.

If you do not have these headers, especially the Access-Control-Allow-Origin header, you will see this error:

XMLHttpRequest cannot load No
'Access-Control-Allow-Origin' header is present on the requested
resource. Origin '' is therefore not allowed access.

Also this can only be tested when using a real domain name, will result in this error:


Unless you do some hacky stuff like alias your localhost’s DNS in /etc/hosts or restart chrome with a special switch to not check certificates or just not use SSL. Testing this stuff can be tricky.