Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

if conditions break try_files in nginx configuration

Tags:

nginx

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!

like image 906
ba0708 Avatar asked Sep 17 '16 15:09

ba0708


1 Answers

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;
}
like image 138
M Somerville Avatar answered Sep 22 '22 16:09

M Somerville