Technology

HTTP security headers: which ones to set on any website and what each does

What HSTS, Content-Security-Policy, X-Content-Type-Options, X-Frame-Options, Referrer-Policy and Permissions-Policy do, with recommended values and checks.

HTTP security headers are instructions the server sends along with every response so the browser applies additional protections: forcing HTTPS (Strict-Transport-Security), limiting where scripts and other resources may be loaded from (Content-Security-Policy), preventing the site from being embedded in other pages (X-Frame-Options / frame-ancestors), stopping the browser from “guessing” file types (X-Content-Type-Options), controlling which referrer information is sent to other sites (Referrer-Policy) and disabling browser APIs you do not use (Permissions-Policy). They are configured on the server or hosting provider, not in the HTML, and a static site can have all of them in minutes.

This article explains what each one does, which value to use on a typical website and how to check that they are active.

Why they matter on a site “with nothing to steal”

A blog with no registered users looks like an unattractive target, but the headers protect your visitors: they prevent a compromised third-party script from injecting code, someone embedding your site in a frame to trick clicks (clickjacking) or a connection downgraded to HTTP allowing content tampering. They are also free in performance terms and signal a carefully maintained site.

1. Strict-Transport-Security (HSTS)

Tells the browser that, for the stated period, it should only connect to your domain over HTTPS, even if the user types http:// or follows an old link.

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
  • max-age=31536000: one year, in seconds.
  • includeSubDomains: applies to subdomains too. Make sure all of them serve HTTPS before enabling it.
  • preload: allows the domain to be included in the preload list built into browsers, so even the first visit never uses HTTP. Only add preload if you are really going to submit the domain to the list; it is hard to reverse.

Start with a short max-age (for example, 300) to check nothing breaks, and raise it afterwards.

2. Content-Security-Policy (CSP)

The most powerful and the most delicate: it defines from which origins the browser may load scripts, styles, images, fonts, connections and iframes. A script injected from an unauthorised origin simply does not run.

A starting point for a static site without third parties:

Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-src 'none'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; upgrade-insecure-requests

Practical notes:

  • 'self' is your own origin. Every external service (analytics, advertising, fonts, videos) must be added explicitly to the relevant directive.
  • Avoid 'unsafe-inline' in script-src: it cancels much of the protection. If you need inline scripts, use nonces or hashes. In style-src it is tolerated more often because many code highlighters and components generate inline styles, although removing it too is the ideal.
  • frame-ancestors 'none' replaces X-Frame-Options in modern browsers.
  • Test first with Content-Security-Policy-Report-Only, which reports violations without blocking anything, and watch the browser console.

3. X-Content-Type-Options

X-Content-Type-Options: nosniff

Prevents the browser from interpreting a file as a type other than the one declared in Content-Type (for example, executing something served as text as a script). Always, without exceptions.

4. X-Frame-Options

X-Frame-Options: DENY

Forbids your page from loading inside another site’s <iframe> (clickjacking protection). SAMEORIGIN allows embedding only from your own domain. Today CSP’s frame-ancestors covers the same more precisely; keep both for older browsers.

5. Referrer-Policy

Referrer-Policy: strict-origin-when-cross-origin

Controls how much of the originating URL is sent in the Referer header when navigating to another site. With this value, other domains only receive the origin (https://blog.aimran.es) and never the full path, and nothing at all when going from HTTPS to HTTP. It is the default in modern browsers, but declaring it avoids depending on that.

6. Permissions-Policy

Permissions-Policy: camera=(), microphone=(), geolocation=(), interest-cohort=()

Disables browser APIs your site does not use, also for any third-party iframe you embed. The list of available features is on MDN; declare the ones you do not need as empty (()).

Summary table

Header Protects against Recommended value
Strict-Transport-Security HTTP connections, protocol downgrade max-age=31536000; includeSubDomains (+ preload if you will register it)
Content-Security-Policy Script injection (XSS), unauthorised resources Start with default-src 'self' and add the origins you need
X-Content-Type-Options Misinterpreted file types nosniff
X-Frame-Options Clickjacking (older browsers) DENY
Referrer-Policy Leaking internal URLs to third parties strict-origin-when-cross-origin
Permissions-Policy Use of sensitive APIs by your site or iframes Disable what you do not use

Where to configure them

  • Netlify and Cloudflare Pages: a _headers file in the published folder.
  • Vercel: the headers key in vercel.json.
  • Nginx: add_header directives in the server block.
  • Apache: Header set directives in the configuration or .htaccess.
  • WordPress: through the server (preferable) or a security plugin that adds them.

The recommendation is to keep them in a single versioned file alongside the code, so that a change to the site (for example, enabling advertising) means reviewing the CSP in the same commit.

How to check them

curl -sI https://blog.aimran.es/en/ | grep -iE "strict-transport|content-security|x-content-type|x-frame|referrer-policy|permissions-policy"

Online tools such as securityheaders.com or Mozilla’s HTTP Observatory analyse the response and grade the configuration. And the browser console shows every CSP violation with the blocked resource, which makes tuning the policy easier.

Common mistakes

  1. Enabling HSTS with preload before every subdomain is on HTTPS: they stop working and reverting takes months.
  2. A CSP with 'unsafe-inline' and 'unsafe-eval' in scripts “so everything works”: it ticks the box and protects little.
  3. Forgetting the CSP when adding a third party (advertising, analytics, a widget): the resource is blocked silently and it looks like “it does not load”.
  4. Setting the headers only on the home page and not on the other routes or static files.
  5. Not testing in report-only mode before blocking.

Conclusion

Six headers, one configuration file and a few minutes: HSTS to force HTTPS, CSP to control what loads, nosniff and X-Frame-Options/frame-ancestors to close classic vectors, Referrer-Policy to stop leaking URLs and Permissions-Policy to switch off what you do not use. Deploy the CSP in report-only mode first, check the console, and version the headers with the code so they evolve with the site.

Sources and references

  1. MDN: Strict-Transport-Security developer.mozilla.org
  2. MDN: Content Security Policy (CSP) developer.mozilla.org
  3. MDN: X-Content-Type-Options developer.mozilla.org
  4. MDN: Referrer-Policy developer.mozilla.org
  5. MDN: Permissions-Policy developer.mozilla.org
  6. OWASP Secure Headers Project owasp.org
  7. HSTS Preload List hstspreload.org
Articles in Technology