1

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!

  • 1
    Have you tried switching the line add_header Content-Type 'text/plain' always; to location @backend block? – Pothi Kalimuthu Jun 5 at 4:07
  • 1
    @PothiKalimuthu That would set 'text/plain' for all backend responses, which I don't want to do. Again, I want to serve 'text/html' as default, and 'text/plain' only for a few specific URIs. In both cases this needs to override the backend's Content-Type. – imiric Jun 5 at 8:52
  • Okay. Got it. It was my misunderstanding, sorry. In that case, you could resolve the issue by having the exact replica of location @backend block into something like location @custom_backend with an extra line add_header Content-Type 'text/plain' always; in the custom_backend block. If this is confusing, please see my answer with the sample code. – Pothi Kalimuthu Jun 6 at 2:31
1

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;
        }
    }
}
  • 1
    Thanks, that does indeed work. I was trying to avoid duplication, but that is easily remedied with include. It's just unintuitive how add_header behaves, and ultimately I reverted to using map to set a custom Content-Type variable, since there's seemingly no way to use this named location approach while retaining the types/mime.types functionality, as this poor fella wanted as well. It's surprising to me that something so basic (overriding backend headers) is so difficult to do with nginx. Thanks again! – imiric Jun 6 at 21:49
  • Good to know that you switched to map. There are indeed multiple ways to solve an issue with Nginx. None of them are clean and easy. However, with the introduction of njs, the future of Nginx customization looks promising. When njs becomes a standard module, there will be a paradigm shift on everything about Nginx. – Pothi Kalimuthu Jun 7 at 0:26
  • 1
    Interesting, I hadn't heard about njs. It sounds like Lua/OpenResty, and FWIW what I wanted to do can be accomplished with Lua, according to this comment, though I haven't tried it. I don't want to add that kind of extensibility/complexity when nginx should be capable of doing it OOB. – imiric Jun 7 at 10:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.