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
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
In this blog post, we will be focusing on 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:
As you can see, there's some room for improvement.
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;
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.
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:
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?