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