14

I have a simple location block in my nginx config which matches static files for my website. What I want to do, is to check if the file exists using try_files, and if it doesn't, redirect to a URL (in this case specified in the @cdn location block). I also want to set some CORS headers.

Below is the relevant configuration.

location ~* \.(css|js|jpe?g|png|gif|otf|eot|svg|ttf|woff|woff2|xml|json)$ { if ($request_method = 'OPTIONS') { add_header 'Access-Control-Allow-Origin' "$http_origin"; add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS'; add_header 'Access-Control-Max-Age' 1728000; add_header 'Content-Type' 'text/plain charset=UTF-8'; add_header 'Content-Length' 0; return 204; } if ($request_method = 'POST') { add_header 'Access-Control-Allow-Origin' "$http_origin"; add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS'; } if ($request_method = 'GET') { add_header 'Access-Control-Allow-Origin' "$http_origin"; add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS'; } try_files $uri @cdn; } location @cdn { return 301 https://example.com$request_uri; } 

The problem is that I get a 404 response if the file does not exist, instead of a 301 redirect. The configuration worked/works fine before adding the CORS headers. If I remove the handling of the headers, everything works as intended, and I get a 301 response back.

Now I have done a bit of reading about why the if directive is bad and should be avoided, but I still don't know why it breaks my configuration. If I understood correctly, it has something to do with either if or add_header being part of a rewrite module or something like that, and I guess that conflicts with try_files. Perhaps I am not accurate here, but either way I am not sure how to fix it.

Why does the presence of if and/or add_header make nginx give me a 404 instead of a 301 when a file could not be found, and how do I fix it? Thanks in advance!

1

2 Answers 2

12

http://agentzh.blogspot.co.uk/2011/03/how-nginx-location-if-works.html might be of interest to you in understanding how if works. In your case, when the if condition matches, the request is now being served within the if context, and try_files is not inherited by that context. Or as https://www.digitalocean.com/community/tutorials/understanding-the-nginx-configuration-file-structure-and-configuration-contexts says "Another thing to keep in mind when using an if context is that it renders a try_files directive in the same context useless."

Also, if the try_files falls back to @cdn then any headers you've added previously are forgotten, it starts again in the new location block, and so the headers need to be added there.

As to how to fix it; you can set variables inside if, and add_header ignores an empty value, so something like this should work:

set $access-control-output 0; location ~* \.(css|js|jpe?g|png|gif|otf|eot|svg|ttf|woff|woff2|xml|json)$ { set $access-control-output 1; try_files $uri @cdn; } set $acao = ""; set $acam = ""; if ($access-control-output) { set $acao = $http_origin; set $acam = "GET, OPTIONS"; } map "$access-control-output:$request_method" $acma { "1:OPTIONS" 1728000; default ""; } location @cdn { add_header 'Access-Control-Allow-Origin' $acao; add_header 'Access-Control-Allow-Methods' $acam; add_header 'Access-Control-Max-Age' $acma; return 301 https://example.com$request_uri; } 

Edit: You don't care about the headers in the @cdn fallback, in which case you should be able to have something like this:

map $request_method $acma { "OPTIONS" 1728000; default ""; } location ~* \.(css|js|jpe?g|png|gif|otf|eot|svg|ttf|woff|woff2|xml|json)$ { add_header 'Access-Control-Allow-Origin' $http_origin; add_header 'Access-Control-Allow-Methods' "GET, OPTIONS"; add_header 'Access-Control-Max-Age' $acma; try_files $uri @cdn; } location @cdn { return 301 https://example.com$request_uri; } 
Sign up to request clarification or add additional context in comments.

2 Comments

Thank you very much for your answer! That behavior is quite surprising, and I would have had a hard time figuring that out. Actually, setting the headers for the @cdn location is not necessary in my particular case. My apologies for not mentioning this in the question - I thought that I did. Could the configuration be simplified further with this in mind? Thanks a lot in advance!
No problem, map is really useful. I came across the question while trying to solve something similar for someone else, so it was very handy :)
0

After years of using Nginx, I did come across this issue, and have found very little discussion, I don't think most devs realize that if conditions conflict with try_files ...

Anyway, another option to @M Somerville's brilliant explanation and solution might be to simply invert the logic of your if statement like here:

https://serverfault.com/a/579739/144798

In that thread/answer, the OP was using if ($args ~ "api_url") { and the suggestion was to invert like if ($arg_api_url != '') { to avoid those requests getting stuck in the if's black hole...

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.