HolicSecurity

Passive security audits · By HolicAudit · PDF in 48 hours · Pay in USDT or PayPal

HolicSecurity · Guide

Security headers explained, in plain English

Published 31 October 2026

In short: security headers are instructions your server sends with every page. They tell the browser what the page may load, whether it may be framed, and whether to insist on HTTPS. They're free, take minutes to add, and are easy to forget. Our own sister site was missing several of them, including CSP and HSTS, when we audited it.

Check your headers in 30 seconds

Open a terminal and run:

curl -sI https://example.com

Every line in the output is a response header. Or open your browser's developer tools, go to the Network tab, reload, click the first request and read Response Headers. Compare what you see with the table below.

HeaderWhat it doesA safe starting value
Content-Security-PolicyLimits where scripts, styles, images and frames can load fromDepends on your site; see below
Strict-Transport-SecurityMakes browsers use HTTPS onlymax-age=31536000
X-Frame-OptionsStops other sites framing your pages (clickjacking)DENY or SAMEORIGIN
X-Content-Type-OptionsStops the browser guessing file typesnosniff
Referrer-PolicyControls how much of your URL is shared with other sitesstrict-origin-when-cross-origin
Permissions-PolicyTurns off browser features you don't usecamera=(), microphone=(), geolocation=()

Content-Security-Policy (CSP)

CSP is a list of sources the browser may load content from. If an attacker manages to inject a <script> into your page, for example through a comment form or a compromised plugin, a good policy stops the browser from running it. It's a second line of defence against cross-site scripting (XSS), not a replacement for fixing the bug.

A strict policy for a site with no third-party scripts looks like this:

Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'

Three tips that save the most time:

Strict-Transport-Security (HSTS)

Even if your site redirects HTTP to HTTPS, the very first request can still go out over plain HTTP, where it can be intercepted on a hostile network. HSTS tells the browser to skip HTTP entirely for your domain for the next max-age seconds.

Strict-Transport-Security: max-age=31536000; includeSubDomains

Add includeSubDomains only when every subdomain works over HTTPS, because browsers will refuse plain HTTP on all of them. Start with a short max-age, such as one day, then raise it to a year once nothing breaks. Some top-level domains, such as .dev, are already HTTPS-only in browsers, but sending the header is still good practice.

X-Frame-Options and frame-ancestors

If another site can load your page inside an invisible frame, it can trick visitors into clicking buttons on your page. This is called clickjacking. Block it with either header; sending both covers older browsers:

X-Frame-Options: DENY
Content-Security-Policy: frame-ancestors 'none'

Use SAMEORIGIN or frame-ancestors 'self' if your own site frames its pages.

nosniff, Referrer-Policy, Permissions-Policy

X-Content-Type-Options: nosniff

Without it, a browser may decide that an uploaded "image" is really a script and run it. nosniff makes the browser trust the declared content type. There's no reason not to send it.

Referrer-Policy

When a visitor clicks a link to another site, the browser can send the full URL they came from, which may include search terms, IDs or tokens. strict-origin-when-cross-origin sends only your domain to other sites and nothing when leaving HTTPS for HTTP.

Permissions-Policy

Switches off browser features your site never uses, so a malicious script or embedded frame can't ask for them either. For most marketing sites:

Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=()

Leave out any feature your site does use. For example, remove payment=() if your checkout uses the browser's Payment Request API.

COOP, CORP and CORS

Headers you should remove

Headers such as Server: Apache/2.4.41, X-Powered-By: PHP/7.4 or X-Generator announce exact software versions. That doesn't make you vulnerable by itself, but it tells anyone scanning the internet which known CVEs to try. Hide the version, keep the software patched, or both. In nginx use server_tokens off;, in Apache ServerTokens Prod, and in PHP expose_php = Off.

Copy-paste config

Cloudflare Pages and Netlify (_headers file)

This is the real file that serves this site. It works as-is for a static site with no scripts. If your site has scripts, add script-src 'self' and the other sources you use.

/*
  Content-Security-Policy: default-src 'none'; style-src 'self'; font-src 'self'; img-src 'self'; frame-src 'self'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; upgrade-insecure-requests
  Strict-Transport-Security: max-age=31536000; includeSubDomains
  X-Frame-Options: DENY
  X-Content-Type-Options: nosniff
  Referrer-Policy: strict-origin-when-cross-origin
  Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=()
  Cross-Origin-Opener-Policy: same-origin
  Cross-Origin-Resource-Policy: same-origin

nginx

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
server_tokens off;

In nginx, add_header lines in a location block replace the ones from the server block instead of adding to them, so repeat them where needed.

Apache (mod_headers)

Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Header always set X-Frame-Options "DENY"
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"

Add a CSP once you've tested it in report-only mode. Then run curl -sI again to confirm every header arrives.