Essential Security Headers Guide (CSP, HSTS, X-Frame-Options)
The security headers you must set, what each does, recommended values and common mistakes.
It is tempting to assume that turning on HTTPS makes a site secure, but in practice you need a handful of response headers to stop common attacks like clickjacking, cross-site scripting (XSS), MIME sniffing, and protocol downgrades. Those few lines are your HTTP security headers — instructions the server sends with each page that tell the browser exactly how to treat it.
This guide walks through the six security headers that matter most in practice: what each one blocks, the recommended value, and the mistake people make most often. At the end it shows how to check which headers your site is actually sending. Configure them once and your score on security graders jumps immediately.
Content-Security-Policy (CSP) — your first line against XSS
CSP restricts, via a whitelist, which origins a page may load scripts, styles, images, and fonts from. Even if an attacker injects a malicious <script> into your markup, the browser refuses to run it unless its origin is allowed by the policy. That makes CSP the strongest header you can set: it adds a layer of defense so that an XSS vulnerability does not automatically become a working exploit. One header can block inline scripts, inline styles, and eval(), restrict form submission targets (form-action), and limit who may frame the page (frame-ancestors).
Because it is the most powerful header, it is also the easiest to break. Too strict and legitimate scripts get blocked, taking the site down; too loose and the protection evaporates. The safe path is to start inContent-Security-Policy-Report-Onlymode, watch the violation reports, then tighten and enforce. Generate a starting policy that matches your site’s structure with the CSP Generator to cut down on trial and error.
Strict-Transport-Security (HSTS) — force HTTPS
When a user types just the domain or visits over http://, the very first request goes out in cleartext. A man-in-the-middle can exploit that brief window to intercept traffic and downgrade the connection. HSTS closes the gap by telling a returning browser to “always use HTTPS for this domain from now on.”
max-age: how long (in seconds) to remember the rule. The recommended value is two years,63072000.includeSubDomains: applies the rule to every subdomain. Only enable it when all of them are HTTPS-only.preload: registers the domain in the browser’s built-in HSTS preload list so that even the first visit never touches HTTP. Removal is slow and hard, so add it deliberately.
Recommended value: Strict-Transport-Security: max-age=63072000; includeSubDomains; preload. If even one subdomain has not been moved to HTTPS yet, drop includeSubDomains so you do not accidentally make that subdomain unreachable.
Clickjacking, MIME, referrer, and permissions headers
The remaining headers are one-liners, but their effect is clear.
- X-Frame-Options / frame-ancestors (clickjacking defense): stops an attacker from layering your page in a transparent
<iframe>and tricking users into clicking hidden buttons. UseX-Frame-Options: DENYto forbid all framing, or CSP’sframe-ancestors 'self'to allow only the same origin. The CSP directive is the newer, preferred standard; many sites keep both for older-browser compatibility. - X-Content-Type-Options: nosniff (block MIME sniffing): stops the browser from ignoring the declared
Content-Typeand guessing a type from the bytes. That prevents a text file from being reinterpreted and executed as a script. The only value isnosniff. - Referrer-Policy (prevent referrer leakage): controls how much of the originating URL leaks to other sites when a user follows an outbound link. The recommended value is
strict-origin-when-cross-origin: the full path within your own origin, only the domain to external sites. - Permissions-Policy (restrict browser features): locks down powerful capabilities like camera, microphone, geolocation, and payment for the page and any embedded iframes. Turn off everything you do not use, e.g.
Permissions-Policy: geolocation=(), camera=(), microphone=().
The headers at a glance: purpose and recommended value
| Header | What it blocks | Recommended value |
|---|---|---|
Content-Security-Policy | XSS, data injection, unwanted framing | default-src 'self'; object-src 'none'; frame-ancestors 'self' (tune per site) |
Strict-Transport-Security | Protocol downgrade, cleartext first request | max-age=63072000; includeSubDomains; preload |
X-Frame-Options | Clickjacking (iframe embedding) | DENY or SAMEORIGIN |
X-Content-Type-Options | MIME sniffing | nosniff |
Referrer-Policy | Referrer URL leakage | strict-origin-when-cross-origin |
Permissions-Policy | Access to unneeded browser features | geolocation=(), camera=(), microphone=() |
How to verify, and common mistakes
Setting the headers is not the end — you must confirm they actually ship in the response and that there are no typos or bad values. The fastest route is to enter your domain into Security Headers Check and see at a glance which headers are present and which are missing. You can also read the response headers directly in your browser DevTools Network tab, or from a terminal with curl -I https://example.com.
These are the mistakes that show up most often in the field.
- An overly broad CSP: adding
script-src 'unsafe-inline','unsafe-eval', or a wildcard*means the header exists but the XSS protection is effectively gone. Rather than whitelisting all inline code because CSP keeps breaking, allow only the inline scripts you need via a nonce or hash. - Adding
includeSubDomains/preloadto HSTS blindly: a single subdomain that is not HTTPS becomes unreachable. preload is very slow to undo, so enable it only after confirming every domain is HTTPS. - Setting headers on HTTP but missing them on HTTPS: when a reverse proxy or load balancer has split configs, headers often attach to only one side. Check both.
- Typos and invalid directives: a misspelling like
X-Frame-Option(missing the s) is simply ignored by the browser. Always confirm the header is really applied with a checker after you deploy it.