Home > Net >  Nginx locations, try_files and headers issue
Nginx locations, try_files and headers issue

Time:05-24

So I've replaced Passenger with Puma for a Rails app, and i just noticed that i now have issues with the cdn assets, they now give CORS errors.

Back when i was using Passenger i had the following configs for Nginx:

server {

  server_name mysite.com;
  root /var/www/mysite.com/public;

  client_max_body_size 4000M;
  passenger_enabled on;
  rails_env production;

  location ~* ^/cdn/ {
    add_header Access-Control-Allow-Origin *;
    expires 364d;
    add_header Pragma public;
    add_header Cache-Control "public";
    break;
  }

  location ~* ^/assets/ {
    # Per RFC2616 - 1 year maximum expiry
    # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
    expires 1y;
    add_header Cache-Control public;

    # Some browsers still send conditional-GET requests if there's a
    # Last-Modified header or an ETag header even if they haven't
    # reached the expiry date sent in the Expires header.
    add_header Last-Modified "";
    add_header ETag "";
    break;
  }

  listen 443 ssl; # managed by Certbot
  #the rest of the certbot ssl stuff

}

I then changed the configs to this to make it work with Puma and unix sockets:

upstream puma {
  server unix:///var/www/mysite.com/shared/sockets/puma.sock;
}
server {

  server_name mysite.com;
  root /var/www/mysite.com/public;

  client_max_body_size 4000M;

  location / {
    try_files $uri @app;
  }

  location ~* ^/cdn/ {
    add_header Access-Control-Allow-Origin *;
    expires 364d;
    add_header Pragma public;
    add_header Cache-Control "public";
    break;
  }

  location ~* ^/assets/ {
    # Per RFC2616 - 1 year maximum expiry
    # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
    expires 1y;
    add_header Cache-Control public;

    # Some browsers still send conditional-GET requests if there's a
    # Last-Modified header or an ETag header even if they haven't
    # reached the expiry date sent in the Expires header.
    add_header Last-Modified "";
    add_header ETag "";
    break;
  }

  listen 443 ssl; # managed by Certbot
  #ssl stuff

  location @app {
    proxy_pass http://puma;

    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header Host $http_host;

    proxy_headers_hash_max_size 512;
    proxy_headers_hash_bucket_size 128;

    proxy_redirect off;
  }

}

This works fine but then i noticed that the cdn urls were giving 404, so i updated the cdn location to this (i added try_files $uri @app;):

  location ~* ^/cdn/ {
    add_header Access-Control-Allow-Origin *;
    expires 364d;
    add_header Pragma public;
    add_header Cache-Control "public";
    try_files $uri @app;
    break;
  }

This now works but i get CORS errors so it seems the headers are not getting set.

My guess is the try_files ignores what was set before it is called, so i tried setting the proxy header for access control inside the location @app (by adding proxy_set_header Access-Control-Allow-Origin *;) but i still get the errors.

What's the correct way to go about this?

CodePudding user response:

There is no sense in trying to add the response headers with the proxy_set_header directive - it is designed for adding/changing headers for the request that will be sent to the upstream. To add the response headers, no matter is it a static or a proxied location, use the add_header one. To add headers conditionally, lets say depending on a request URI, you can use the map block(s):

map $uri $expires {
    ~^/cdn/  1y;
    default  off;
}
map $uri $cache_control {
    ~^/cdn/  public;
    # default will be an empty value
}
map $uri $allow_origin {
    ~^/cdn/  *;
    # default will be an empty value
}

However in terms of performance, since all the map-derived variables are evaluated only once per request, matching regex pattern only once can be slightly more performant:

map $cache $expires {
    1        1y;
    default  off;
}
map $cache $cache_control {
    1        public;
}
map $cache $allow_origin {
    1        *;
}
map $uri $cache {
    ~^/cdn/  1;
}

Next, in your @app location you can use the following:

location @app {
    proxy_pass http://puma;

    expires $expires;
    add_header Cache-Control $cache_control;
    add_header Access-Control-Allow-Origin $allow_origin;

    # ... proxy_set_... and other upstream setup here
}

If evaluated variable used in an add_header directive will be empty, nginx won't add a header with an empty value - instead it won't add such a header at all.


A few notes about your current config:

  • Using those kind of regex locations like location ~ ^/cdn/ { ... } or location ~ ^/assets/ { ... } in favor of prefix locations location /cdn/ { ... } or location /assets/ { ... } makes no sense and only a performance impact (due to the PCRE library is involved when it isn't nessesary).

  • That break directive at the end of static locations does nothing since there are no any directives from the rewrite module there which execution should be stopped.

  • Related