Skip to content
OneKitTools logoOneKitTools
security6 min read

"Security Headers Every Website Needs in 2026 (CSP, HSTS, and More)"

The essential HTTP security headers explained — CSP, HSTS, X-Frame-Options, Permissions-Policy — with Nginx and Apache configs and a free audit workflow.

OneKitTools TeamJuly 10, 2026

Why Security Headers Still Matter in 2026

Most website compromises don't start with a zero-day. They start with something boring: an injected script, a page loaded inside a malicious iframe, a browser guessing a file type it shouldn't. HTTP security headers are the cheapest defense you'll ever deploy — a few lines of server config that tell browsers exactly what your site is allowed to do.

The catch? Surveys of the top million sites consistently show that fewer than half send a Content-Security-Policy, and many that do ship one so permissive it's decorative. Let's fix that.

The Essential Headers, and What Each One Blocks

Every header below maps to a specific class of attack. If you remember nothing else, remember this table:

HeaderBlocksRecommended value
Content-Security-PolicyXSS, injected scripts, data exfiltrationSite-specific (see below)
Strict-Transport-SecurityHTTPS downgrade, SSL strippingmax-age=31536000; includeSubDomains; preload
X-Content-Type-OptionsMIME sniffing attacksnosniff
X-Frame-OptionsClickjacking (legacy browsers)DENY or SAMEORIGIN
Referrer-PolicyURL leakage to third partiesstrict-origin-when-cross-origin
Permissions-PolicySilent abuse of camera, mic, geolocationcamera=(), microphone=(), geolocation=()

A few notes on the ones people get wrong:

Strict-Transport-Security (HSTS)

HSTS tells the browser: never talk to this site over plain HTTP again. Without it, a user typing yoursite.com makes one unencrypted request first — and on hostile Wi-Fi, that single request is enough for an SSL stripping attack. Start with a short max-age (say, 300) while testing, then raise it to a year. Only add preload once you're certain every subdomain serves HTTPS — preload is very hard to undo. And obviously, HSTS is pointless if your certificate is broken; verify yours with an SSL Checker first.

X-Frame-Options vs frame-ancestors

These two overlap, and that confuses people. X-Frame-Options: DENY is the legacy header that stops your page from being framed. Its modern replacement is the CSP directive frame-ancestors 'none', which is more flexible (it accepts a list of allowed origins). When both are present, browsers that support CSP ignore X-Frame-Options. Best practice in 2026: send bothframe-ancestors for modern browsers, X-Frame-Options as a fallback for older ones.

X-Content-Type-Options

One value, no options: nosniff. It stops browsers from "sniffing" content and executing a file as JavaScript because it looks like JavaScript, even when your server said it was plain text. It's a one-liner that closes an entire attack class. There is no reason not to send it.

Content-Security-Policy: The Deep Dive

CSP is the heavyweight. It's a whitelist of everything your page may load — scripts, styles, images, fonts, frames, connections. A solid starting point looks like this:

Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self'; img-src 'self' data: https:; font-src 'self'; connect-src 'self' https://api.example.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self';

Key directives to understand:

  • default-src — the fallback for everything you don't specify. Always set it to 'self' or 'none'.
  • script-src — the most important one. This is your XSS shield.
  • base-uri and form-action — often forgotten, they prevent attackers from redirecting form submissions or rewriting relative URLs.
  • frame-ancestors — clickjacking protection, as covered above.

Nonces vs unsafe-inline

The single worst thing you can put in a CSP is script-src 'unsafe-inline'. It re-opens the exact door CSP exists to close: any script injected into your HTML runs. If you have inline scripts you can't move to files, use nonces instead:

<!-- Server generates a fresh random nonce per response -->
<script nonce="r4nd0mB4se64">
  initApp();
</script>
Content-Security-Policy: script-src 'nonce-r4nd0mB4se64' 'strict-dynamic';

The nonce must be unpredictable and unique per request — an attacker who can guess it can bypass the policy. Pair it with 'strict-dynamic' and scripts loaded by your trusted scripts are allowed too, which makes modern bundlers and tag managers much less painful.

Report-Only: Roll Out Without Breaking Production

A strict CSP deployed blindly will break something — an analytics snippet, a payment iframe, a font you forgot. That's what Content-Security-Policy-Report-Only is for. Same syntax, but violations are reported, never enforced:

Content-Security-Policy-Report-Only: default-src 'self'; report-uri https://yoursite.com/csp-reports;

Run report-only for a week or two, review the violations, whitelist what's legitimate, then flip to enforcing. Building the policy itself is much easier with a CSP Generator — pick your sources per directive and copy the result instead of hand-assembling directive strings.

Nginx and Apache Config Snippets

Nginx — inside your server block:

add_header Content-Security-Policy "default-src 'self'; frame-ancestors 'none'; base-uri 'self'" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;

The always flag matters: without it, Nginx skips your headers on error responses (404, 500), and those pages are just as attackable.

Apache — in your vhost or .htaccess, with mod_headers enabled:

Header always set Content-Security-Policy "default-src 'self'; frame-ancestors 'none'; base-uri 'self'"
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "DENY"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"

How to Audit Your Current Headers

Before adding anything, know where you stand. Three quick steps:

  1. Scan your security posture. Run your domain through a Security Headers Checker. You'll get a header-by-header verdict: what's present, what's missing, and what's misconfigured.
  2. Inspect the raw response. An HTTP Headers Checker shows you every header your server actually sends — useful for catching duplicates (two CSPs = the intersection is enforced, which surprises people) or headers injected by a CDN you forgot about.
  3. Verify your TLS foundation. HSTS and secure cookies assume your HTTPS setup is healthy — check expiry, chain, and protocol with the SSL checker.

Re-run the scan after every deploy that touches your reverse proxy. Headers regress silently: a CDN migration or a new load balancer can wipe them out with no visible symptom.

Common Mistakes to Avoid

  • Setting CSP but keeping unsafe-inline and unsafe-eval — you've built a fence with the gate open.
  • HSTS preload on day one — get everything on HTTPS first; preload removal takes months.
  • Headers only on the homepage — misconfigured routing often leaves API routes or error pages unprotected.
  • Deprecated headersX-XSS-Protection is obsolete and can even introduce vulnerabilities in old browsers; set it to 0 or drop it.

Check Your Site in 30 Seconds

You now know what each header does and what a good value looks like. The next step takes half a minute: paste your URL into the Security Headers Checker and see exactly which protections you're missing. Free, instant, no account required — then use the CSP Generator to build the policy that closes the gaps.

Share