The Problem With Nginx Add_header

Sunday, December 13, 2020 • 3 minutes to read

I often have a problem using nginx’s add_header directive, especially with a complex configuration, including multiple virtual hosts. When I test the configuration, everything seems okay, and the nginx does not complain about any syntax. However, when checking the HTTP headers returned by the web server, it looks like it does not work.

I use multiple add_header directives, and it is not working.

Let’s take a scenario where we have a simple nginx configuration. Globally for all current and future virtual hosts, we want to prevent web browsers from MIME-sniffing the response. We, of course, are using add_header directive on the http level.

http {
    add_header  X-Content-Type-Options nosniff;

    include  /etc/nginx/conf.d/*.conf;
}

We want to prevent proxies and caches from modifying the website’s content on the’ server’ level.

server {
    listen  80;
    server_name  localhost;

    add_header  Cache-Control "no-transform";
}

Surprisingly, after reloading nginx’s configuration, when we want to test if we have correct HTTP headers, only one of them is available.

$ curl -I localhost:80
HTTP/1.1 200 OK
Server: nginx/1.18.0
Date: Sun, 13 Dec 2020 14:16:59 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Thu, 29 Oct 2020 15:25:17 GMT
Connection: keep-alive
ETag: "5f9adedd-264"
Cache-Control: no-transform
Accept-Ranges: bytes

Why are multiple add_headers in nginx not working?

What could be wrong? Is using add_header on multiple levels not supported? According to nginx’s documentation, we could use add_header on the http, server, and location levels. If that were a problem, nginx would complain about a syntax error in the configuration.

The part of the documentation which I always miss is the following one:

These directives are inherited from the previous configuration level if and only if there are no add_header directives defined on the current level.

We see only the latest HTTP header because we use add_header on both http and server levels.

What is the solution?

For small and simple configurations, the easiest way to prevent such problems is to keep using add_header on the lowest level possible.

We could move all global HTTP headers to an external file for more complex configurations and include it on server levels.

include  /etc/nginx/conf.d/custom_headers.conf;

Now we receive all HTTP headers. It works as we include all headers only on server, or if needed, on location level.

$ curl -I localhost:80
HTTP/1.1 200 OK
Server: nginx/1.18.0
Date: Sun, 13 Dec 2020 14:47:32 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Thu, 29 Oct 2020 15:25:17 GMT
Connection: keep-alive
ETag: "5f9adedd-264"
X-Content-Type-Options: nosniff
Cache-Control: no-transform
Accept-Ranges: bytes

What about HTTP headers in error responses?

According to documentation, the add_header directive adds headers to most of the successful response codes. A quick check would confirm that a 404 error returns none of the HTTP headers we set previously.

$ curl -I localhost:80/404.html
HTTP/1.1 404 Not Found
Server: nginx/1.18.0
Date: Sun, 13 Dec 2020 14:48:39 GMT
Content-Type: text/html
Content-Length: 153
Connection: keep-alive

Of course, not all headers are necessary for error responses. If we want to make sure some or all of them will also be included in error responses, we need to add the always keyword to the directive.

add_header  Cache-Control "no-transform" always;
$ curl -I localhost:80/404.html
HTTP/1.1 404 Not Found
Server: nginx/1.18.0
Date: Sun, 13 Dec 2020 14:49:06 GMT
Content-Type: text/html
Content-Length: 153
Connection: keep-alive
Cache-Control: no-transform
operationsnginxlinux
See Also
This site uses cookies to analyze traffic and for ads measurement purposes according to your browser settings. Access to those cookies is shared with Google to generate usage statistics and detect and address abuse. Learn more about how we use cookies.