When a web browser sends a request to a web page, it usually gets some HTML code back together with Response Headers as a part of that response. You've probably seen those headers before in the Network tab of your browser. If not, now is the perfect moment to do so :) Or if you prefer a command-line approach, you can use:

curl -I http://www.google.com

Sample response:

TTP/1.1 200 OK
Content-Type: text/html; charset=ISO-8859-1
P3P: CP="This is not a P3P policy! See g.co/p3phelp for more info."
Date: Tue, 12 Jan 2021 14:18:17 GMT
Server: gws
X-XSS-Protection: 0
X-Frame-Options: SAMEORIGIN
Transfer-Encoding: chunked
Expires: Tue, 12 Jan 2021 14:18:17 GMT
Cache-Control: private
Set-Cookie: ...

Response headers are used for different things: caching policies, redirects, authentication...

For a complete list of HTTP headers, please visit
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers

In this blog post, we will be focusing on security headers.

Security Headers

What are the security headers?

Security headers are used to control resources the web browser is allowed to load for a given page, force HTTPS communication instead of HTTP, prevent other domains from opening/controlling a browser window, and much more.

To quickly analyze response headers of your website, check out  https://securityheaders.com/

In the previous blog post, I walked you through the setup of this blog on Amazon. After getting my blog up and running, I decided to do a quick scan on securityheaders.com, and this is the result I got:

security headers - d rating

As you can see, there's some room for improvement.

Nginx

I first spent a few minutes to see if there's a built-in functionality or an addon/plugin that allows me to manage security headers using Ghost.
Since such a thing didn't exist, I decided to give Nginx a try.

Open the nginx.conf file for editing:

sudo nano /etc/nginx/nginx.conf

And add the following lines inside appropriate server block:

add_header Strict-Transport-Security "max-age=31536000";
add_header Referrer-Policy "no-referrer";
add_header Permissions-Policy "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()";
add_header X-Content-Type-Options "nosniff";
add_header X-Frame-Options "sameorigin";
add_header X-Xss-Protection "1; mode=block";
proxy_hide_header X-Powered-By;
server_tokens off;

Server version

There's no built-in way of excluding server info from response headers. server_token off will still return nginx but without version numbers.
That's good enough for a personal blog; other websites would require more work (especially in the bank and finance industry).

Content Security Policy

The most difficult one-liner:

add_header Content-Security-Policy "...'";

Other security headers are straightforward to set up; this one, however, can be quite tricky.

Why?

Well, first of all, you want to block all inline scripts and CSS styles and explicitly whitelist the external resources.

Ghost's default theme, Casper, contains many inline styles and scripts, which I had to move to separate files.

Things like GoogleTagManager require inline scripts whish is not CSP compliant as you have no control over things that can be injected.

And so on and so forth....

In the end, I managed to get my blog CSP compliant.

Here's the result:

security headers - a+ rating

I won't post my Content Security Policy here as it will change as I add more integrations and make changes to the current template.

You can always use curl or some other tool to see response headers:

curl -I https://dejan.blog

Under no circumstances should you allow unsafe-inline in your content security policies!

Remember, there's always a solution! :)

What's your score on securityheaders.com?