Skip to content

headers · July 10, 2026 · 7 min read

HTTP Security Headers Explained: CSP, HSTS, and the Rest

Plain-English guide to HTTP security headers: CSP, HSTS, X-Frame-Options and more — recommended values, checks, and copy-paste fixes for your platform.

Security headers are the cheapest security upgrade your site will ever get: a few lines of configuration that tell every visiting browser how to protect your users. Most sites ship without them — not because they are hard to add, but because nothing visibly breaks when they are missing. Here is what each header does, the value to set, and how to add it on the platform you actually deploy to.

What security headers actually do

Every HTTP response your server sends can carry instructions for the browser: only run scripts from these sources, never show this page inside someone else’s iframe, refuse plain HTTP from now on. The browser enforces those rules on the user’s machine, so even when something else goes wrong — an injected script, a sneaky iframe, a downgraded connection — the damage is contained.

Two things follow from that. First, headers are not your primary defense; they are the seatbelt for the day the primary defense fails. Second, they are fully visible from the outside. Attackers run automated scanners against the entire internet, and missing headers are among the first things those scanners record. “Nobody knows my site exists” is not a plan.

Content-Security-Policy: the one worth learning

CSP is an allowlist for everything your page loads: scripts, styles, images, frames. A good policy turns a cross-site scripting bug from a stolen session into a blocked request and a console warning.

A weak policy is decoration. The usual ways a CSP undermines itself:

  • unsafe-inline in script-src. Allows any inline script to execute — and injected scripts are inline scripts. Use nonces (a random value your server stamps on each legitimate script tag) or hashes instead.
  • unsafe-eval. Lets string-to-code functions run, which injected code loves. Most modern frameworks no longer need it.
  • Wildcard and scheme-only sources. *, data:, or http:in a script source means “load code from anywhere that matches,” which defeats the point.

Three directives people forget, all of which matter:

  • frame-ancestors 'none' (or 'self') — controls who may embed your page in an iframe. This is the modern replacement for X-Frame-Options.
  • base-uri 'self' — stops an injected <base> tag from silently rewriting every relative URL on your page.
  • object-src 'none' — blocks legacy plugin content nobody legitimately uses anymore.

A strict, realistic starting point:

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

Deploy it as Content-Security-Policy-Report-Only first, watch what would break, then enforce.

Strict-Transport-Security: HTTPS, permanently

HSTS tells the browser to refuse plain HTTP for your domain, closing the window where a first request can be intercepted before the redirect to HTTPS happens.

Roll it out in tiers, because it is hard to un-set:

  1. Test: max-age=86400 (one day) — confirm nothing on the domain still needs HTTP.
  2. Commit: max-age=31536000 (one year), and add includeSubDomains once every subdomain serves HTTPS.
  3. Preload: add preload and submit your domain to the browser preload list, which requires the one-year max-age plus includeSubDomains. Preloaded domains are HTTPS-only in browsers before the first visit ever happens.

HSTS assumes your TLS setup is solid; if it is not, fix that first — see SSL/TLS mistakes that still break websites.

The supporting cast

Each of these is one line and worth setting:

  • X-Content-Type-Options: nosniff — stops browsers from guessing file types, so a “harmless” upload cannot be reinterpreted as executable script.
  • Referrer-Policy: strict-origin-when-cross-origin — other sites learn which site a visitor came from, not the full URL (which can contain tokens and private paths).
  • Permissions-Policy: camera=(), microphone=(), geolocation=() — declares which browser features your site and its embedded third parties may use.
  • X-Frame-Options: DENY — the older anti-clickjacking header. frame-ancestors supersedes it, but keeping both costs nothing and covers older browsers.
  • COOP / CORP / COEP Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Resource-Policy: same-origin isolate your pages and resources from cross-origin windows. COEP is stricter and mainly needed for features like SharedArrayBuffer; enable it deliberately, since it can break third-party embeds.

Headers you should remove

Some headers help attackers more than users. Server: nginx/1.18.0, X-Powered-By: Express, and X-AspNet-Version announce your exact stack and version — precisely what automated scanners use to pick a known exploit for that version. Turn them off: server_tokens off; in nginx, poweredByHeader: false in Next.js.

X-XSS-Protection is deprecated. The browser filter it controlled is gone, and old implementations caused problems of their own. Remove it and rely on CSP.

How to check your headers

Open DevTools → Network, click the first request, and read the response headers. Or from a terminal:

curl -sI https://yourdomain.com

Compare against the values above, and test the HTTPS version of your site — some platforms only attach headers there.

How to fix them on your platform

Next.js (next.config.js):

module.exports = {
  poweredByHeader: false,
  async headers() {
    return [{
      source: '/(.*)',
      headers: [
        { key: 'Strict-Transport-Security', value: 'max-age=31536000; includeSubDomains' },
        { key: 'X-Content-Type-Options', value: 'nosniff' },
        { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
        { key: 'Content-Security-Policy', value: "default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'" },
      ],
    }];
  },
};

Vercel / Netlify: set the same values in vercel.json (headers) or netlify.toml ([[headers]]) — this also works for static sites with no server code.

nginx:

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
server_tokens off;

If an AI tool built your app — Cursor, Lovable, Bolt, v0 — headers are exactly the kind of thing generated code skips, because the app works perfectly without them. Our vibe-coder’s guide covers the other gaps that pattern creates.

How BoringSec checks this

BoringSec’s headers scanner — one of the applicable base URL modules — runs 15 header checks. Ten security headers are graded with weights, so a missing CSP costs more than a missing Referrer-Policy. The CSP itself is analyzed per directive, the way Google’s CSP Evaluator does it: unsafe-inline without a nonce or hash, unsafe-eval, wildcard / data: / http: script sources, and missing frame-ancestors, base-uri, or object-src are each flagged individually. HSTS is graded by max-age, including a preload-eligibility check, and X-Frame-Options counts as satisfied if your CSP already sets frame-ancestors — no double-dinging. Four more checks catch version-disclosing headers like Server and X-Powered-By.

Every recommendation ships as a copy-paste header snippet, and every finding cites its standard (OWASP, MDN, RFC). Findings follow our evidence-first rule — a critical or high issue without captured proof is auto-downgraded to medium, because we don’t sell fear. Details in the methodology.

FAQ

Do security headers slow my site down?

No. They add a few hundred bytes per response, and all enforcement happens inside the browser. There is no measurable performance cost.

Which security headers should I add first?

Start with the one-liners: HSTS, X-Content-Type-Options, Referrer-Policy, and frame-ancestors. Add CSP last — it is the most valuable, and the only one that needs testing before you enforce it.

Can a Content-Security-Policy break my site?

Yes, if you enforce a strict policy blind. Deploy it as Content-Security-Policy-Report-Only first, review the violation reports, then switch to enforcing.

Keep reading

Check your site now

One scan applies the relevant checks from 20 base URL modules plus 3 verified-owner background engines. Free, no signup, with base results appearing first.

Scan your site free