Nginx Reverse Proxy – How to Override Backend Header with Named Location

http-headersnginxreverse-proxy

I'm having a difficult time getting add_header to work when try_files is used to send requests to a named location which specifies a backend with proxy_pass.

Basically, I want to override Content-Type for a specific set of URIs, but don't want to use a map to set a variable for it (since I'd like the types and mime.types mechanism to continue working as is), nor to set an extension on the URIs and add more types declarations.

Here's a simplified version of my nginx.conf:

http {
    include       mime.types;
    default_type  text/html;
    charset       utf-8;

    server {
        listen 80;

        location @backend {
            rewrite ^ /proxy$uri break;
            proxy_pass https://backend;
            proxy_intercept_errors on;

            aws_access_key ***;
            aws_secret_key ***;
            s3_bucket ***;
            chop_prefix /proxy;

            proxy_set_header Authorization $s3_auth_token;
            proxy_set_header x-amz-date $aws_date;
            proxy_hide_header Content-Type;
        }

        location / {
            try_files false @backend;
        }

        location ~ /(textfile|anothertextfile)$ {
            try_files false @backend;
            add_header Content-Type 'text/plain' always;
            # This has no effect either
            # default_type text/plain;
        }
    }
}

I'm using ngx_aws_auth here, but I don't think that should matter.

The behavior I've seen with nginx 1.16.0 is that Content-Type is not returned at all; not for the /textfile location where I expect text/plain, nor for any other URL where I expect text/html because of the default_type at the http level. If I remove the proxy_hide_header Content-Type line then I simply get the backend's header, which is what I want to override.

I understand the frankly unintuitive behavior of add_header where headers aren't inherited from a higher level if add_header is specified at the current level, which I don't think is happening here, but I also tried moving all header directives to a standalone .conf file and including it everywhere, and I still get the same behavior.

I also tried using the headers-more module, with no difference.

What am I missing, Server Fault?

Thanks!

Best Answer

The solution is to use an additional named block to have a custom header for all the requests pass through it. The code in the original question didn't work because add_header is effective only on the last matched location block. If a request passes through multiple location blocks, it doesn't pick up add_header directive on the passed location blocks. Nginx only considers or looks for add_header in the last matched location block. In this case, the named location block is the last matched location block. I hope that clarifies why the original code didn't work as expected.

http {
    include       mime.types;
    default_type  text/html;
    charset       utf-8;

    server {
        listen 80;

        location @backend {
            rewrite ^ /proxy$uri break;
            proxy_pass https://backend;
            proxy_intercept_errors on;

            aws_access_key ***;
            aws_secret_key ***;
            s3_bucket ***;
            chop_prefix /proxy;

            proxy_set_header Authorization $s3_auth_token;
            proxy_set_header x-amz-date $aws_date;
            proxy_hide_header Content-Type;
        }

        location @plain_backend {
            rewrite ^ /proxy$uri break;
            proxy_pass https://backend;
            proxy_intercept_errors on;

            aws_access_key ***;
            aws_secret_key ***;
            s3_bucket ***;
            chop_prefix /proxy;

            proxy_set_header Authorization $s3_auth_token;
            proxy_set_header x-amz-date $aws_date;
            proxy_hide_header Content-Type;

            add_header Content-Type 'text/plain' always;
        }

        location / {
            try_files false @backend;
        }

        location ~ /(textfile|anothertextfile)$ {
            try_files false @plain_backend;
        }
    }
}