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 suchContent-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.