Security / Server Setup

NGINX Security Headers, the right way

by , , revisited on


We have by far the largest RPM repository with dynamic stable NGINX modules and VMODs for Varnish 4.1 and 6.0 LTS. If you want to install NGINX, Varnish, and lots of useful modules for them, this is your one-stop repository to get all performance-related software.
You have to maintain an active subscription in order to be able to use the repository!

HTTP header basics

The HTTP headers are the additional information passed between the client (browsers) and web servers.

The request HTTP headers

The request HTTP headers communicate the essential request information based on browser features, user preferences, and navigation intent, like:

  • The hostname of the website being opened, in the Host: request header, e.g. Host: example.com
  • The supported compression methods, via Accept-Encoding: request header, e.g. Accept-Encoding: br, gzip

Request HTTP headers are specified as key-value pairs. They are sent to the server while making requests, like this:

Host: example.com
Accept-Encoding: br, gzip
… other headers …

Any time you browse a web page, your browser sends some HTTP headers and the server will process them, to give you the page your browser can understand.

The response HTTP headers

The response HTTP headers, similar to request HTTP headers are key-value pairs that are essential for HTTP message exchange.
But they are sent in the opposite direction: from the server to the client.
Together, they constitute the header of the HTTP response.
The HTTP header is always present in a response, while the body (e.g. HTML payload) is optional.

The response headers communicate the information about the payload, and the server itself. Examples are:

  • Status: 200 to indicate to the browser that the request was successful.
  • Content-Type: text/html so the browser knows that a given response is an HTML page that is meant for being rendered as such
  • Content-Encoding: br which tells the browser that the response was compressed with the Brotli compression algorithm.
    Having that, the browser knows which decompression method to use.

So the response headers make up the crucial information that describes the response.

The security HTTP headers

The security HTTP headers are the response HTTP headers, that server can add in order to harden the security of HTTP exchange (browsing).

There are a few, and as the web evolves, more are being added. Each security header serves its own purpose.

  • HTTP Strict Transport Security (HSTS)
  • Public Key Pinning Extension for HTTP (HPKP)
  • X-Frame-Options
  • X-XSS-Protection
  • X-Content-Type-Options
  • Content-Security-Policy
  • X-Permitted-Cross-Domain-Policies
  • Referrer-Policy
  • Expect-CT
  • Feature-Policy

In most cases, HTTP security headers are added to responses, so that the browsers behave in a more secure way.

For example:

X-Content-Type-Options: nosniff

When this header is sent in a response, it prevents browsers from trying to “guess” MIME types and such, forcing them to use what the server tells them.
This helps to harden security because a maliciously changed file on a compromised website, has fewer chances to be run as an executable, thus prevents the infecting of the client machines.

We won’t touch on every header and its purpose. But let’s review how to add those headers in a reliable and consistent way, via NGINX configuration.

Adding Security Headers in NGINX

There are several ways you can accomplish the addition of security headers in your NGINX configuration.
Let’s review them, from the worst to the best way.

Using add_header directive

The add_header is the built-in directive in NGINX. However, it is the least intuitive in the way it is inherited, as well as limited in how it can work.
Let’s review an example:

server {
    server_name example.com;
    # Some security headers...
    add_header X-Frame-Options SAMEORIGIN;
    add_header X-XSS-Protection "1; mode=block";

    location /foo/ {
        add_header foo bar;
    }
}

Due to the way the add_header directive works, in this example, there will be only foo: bar header set, and not the others.
This is the common pitfall of virtually any “array-like” NGINX configuration directives: when you add one to the inner scope, all the others will be “lost”.

To make it work using add_header, you must copy all headers that apply to a location explicitly:

server {
    server_name example.com;
    # Some security headers...
    add_header X-Frame-Options SAMEORIGIN;
    add_header X-XSS-Protection "1; mode=block";

    location /foo/ {
        add_header foo bar;
        add_header X-Frame-Options SAMEORIGIN;
        add_header X-XSS-Protection "1; mode=block";
    }
}

This doesn’t make for a very clean configuration. So let’s proceed to next way to do it.

Using headers-more NGINX module

With the Headers-More module you can avoid the configuration pitfalls of the add_header, because it has no “lost header” behavior.
Installation using our repository is straightforward:

sudo yum -y install https://extras.getpagespeed.com/release-latest.rpm
sudo yum install nginx-module-headers-more

Then ensure that is is enabled by placing at the top of your nginx.conf:

load_module modules/ngx_http_headers_more_filter_module.so;

To set headers using this module, you will use more_set_headers directive.

The added feature is that you can specify MIME types for which a given header has to be added.

more_set_headers -t 'text/html application/xhtml+xml text/xml text/plain' "X-Frame-Options: SAMEORIGIN";

Using ngx_security_headers NGINX module

The ngx_security_headers makes the addition of security headers super easy.

Similarly to the previous module, it can be installed easily for CentOS/RHEL via:

sudo yum -y install https://extras.getpagespeed.com/release-latest.rpm
sudo yum -y install nginx-module-security-headers

Then ensure that is is enabled by placing at the top of your nginx.conf:

load_module modules/ngx_http_security_headers_module.so;

Now, simply add security_headers on; this to the http {} section of your NGINX configuration, and you’re done adding all the prominent security headers:

http {
    security_headers on;
    ...
}

This will automatically add a set of security headers that suit most websites:

X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Referrer-Policy: strict-origin-when-cross-origin
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload

How is it better than Headers-More?

  • Plug-n-Play: the default set of security headers can be enabled with simple security_headers on; in your NGINX configuration
  • Sends HTML-only security headers for relevant types only, not sending for others, e.g. X-Frame-Options is useless for CSS
  • Plays well with conditional GET requests: the security headers are not included there unnecessarily
  • Hides X-Powered-By and other headers which often leak software version information

Additionally, you can instruct the security headers module to hide the Server header.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: