RT Cunningham

Nginx add_header Directive – Adding the Headers in the Right Place

Nginx add_header The instructions for the add_header directive with the Nginx web server aren’t very clear. Why else would I have made some horrible mistakes with it?

In a nutshell, only those headers set at the “location” level worked. Until today, that is. It took me a couple of hours to figure out what I was doing wrong.

The Nginx add_header Directive

The pertinent part of the directive states:

There could be several add_header directives. These directives are inherited from the previous level if and only if there are no add_header directives defined on the current level.

With Nginx, most directives require a name/value pair. A colon at the end of a name will make it fail. Many examples exist showing the colon. I had a colon at the end of one and it caused an error page that took me days to correct. To make things worse, I discovered the header wasn’t even being sent.

The context for the directive is: http, server, location, if in location

I had two headers at the http level, a few at the server level and one at the location level. Only the one at the location level worked.

The secret, as it turns out, is to put all the directives at the location level. Sure, it’s going to be repetitive if you have multiple server blocks (multiple domains or subdomains). It’s a good idea to be repetitive in this case.

My Directives

I’m sharing my block of directives at the location level. Perhaps this will help you avoid the mistakes I made:

add_header Cache-Control "max-age=3600, public";
add_header Vary Accept-Encoding;
add_header X-XSS-Protection "1; mode=block";
add_header Strict-Transport-Security "max-age=31536000; includeSubdomains; preload;";
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header Content-Security-Policy "default-src 'self';";
add_header Referrer-Policy same-origin;
add_header Expect-CT "enforce; max-age=3600";

If you’re not using HTTP Strict Transport Security (HSTS), you can leave out the line for it. If you’re not using SSL (HTTPS), you can leave out the last line. It’s for certificate transparency, a new header that probably won’t be required until early next year.

Explanations for all these headers, except for the first two, can be found when you test your site at securityheaders.io.

The Oldest Mistake

I set up HSTS in 2016, months before I wrote about it. It probably worked correctly until I added the “Cache-Control” header at the location level later on. I should have noticed when it stopped working.

When I typed “rtcx.net” alone in a web browser, I should’ve been redirected to “https://www.rtcx.net”. I had all the server blocks set up correctly. It would redirect me to “https://rtcx.net” and then tell me the site was unavailable. It only worked correctly when I typed “www.rtcx.net”. After moving all the directives, the redirects started working as expected.

I don’t know what prompted me to start investigating this today. Regardless, I’m glad I finally know the right place to set the headers. I hate making mistakes like this because I don’t know how they affect things beyond the web server. Maybe they don’t affect more than what I already discovered. I’ll have to wait and see what happens next, I suppose.

Quick Update the Next Morning

I checked my visitor stats and found things that didn’t make sense. I disabled the security headers until I can test and find out which one is causing issues – one by one.


RT Cunningham
October 1, 2017
Web Development