Security & Privacy

HTTP Security Headers Every Site Should Send

A short list of security headers worth sending, a sensible value for each, the outdated ones to drop, and how to roll out a Content-Security-Policy safely.

  • By Jacob
  • 6 min read
A white rounded card with small blue tabs along its top edge and a navy shield standing in front of it, next to a lime green bead

Security headers are short instructions your server sends with every response, telling the browser how to treat your page: only ever load it over HTTPS, don't let other sites frame it, don't run scripts from places you didn't approve. They cost nothing to send and block whole classes of attacks, but only if they're set correctly.

Here's the short list worth sending, a sensible value for each, the old headers you can drop, and how to roll out the tricky one without breaking your site.

Check what you send today

Start by looking at your current response headers. Open the browser's developer tools, go to the Network tab, reload, click the page request and read the response headers. Or enter your URL in our HTTP Headers Viewer, which lists every header, follows redirects, and flags missing or weak security headers and cookie flags.

Check a few different pages, not just the homepage. Headers set by an application often differ between the site, its API and static files.

The headers worth sending

Strict-Transport-Security (HSTS)

HSTS, defined in RFC 6797, tells browsers to use HTTPS for your site for a set time, even if someone types http:// or follows an old link. That closes the window where a first plain-HTTP request could be intercepted and redirected.

Strict-Transport-Security: max-age=31536000; includeSubDomains
  • max-age is in seconds. 31536000 is one year. Our headers tool warns below six months.
  • includeSubDomains covers every subdomain. Add it only when every subdomain works over HTTPS, including old ones like a mail or staging host.
  • preload asks to be built into browsers' preload lists through hstspreload.org. It's hard to undo, so add it last, once you're sure.

The OWASP HTTP Headers Cheat Sheet recommends max-age=63072000; includeSubDomains; preload and warns that a misconfigured HSTS header or a certificate problem can lock legitimate users out. Start with a short max-age, then raise it. HSTS only works on responses sent over HTTPS, so keep a 301 redirect from HTTP to HTTPS as well (301 vs 302 Redirects explains why it should be permanent).

Content-Security-Policy (CSP)

CSP lists where the page may load scripts, styles, images, frames and connections from. If an attacker manages to inject a <script> tag, a good CSP stops the browser from running it. It's the most powerful header on this list and the only one that takes real work.

A solid starting point for a site that serves its own scripts:

Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-RANDOM'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'
  • default-src 'self': by default, load resources only from your own origin.
  • script-src 'self' 'nonce-RANDOM': allow your own script files, plus inline scripts carrying a nonce, a random value generated fresh for every response.
  • object-src 'none': no plugins.
  • base-uri 'self': stops injected <base> tags from redirecting relative URLs.
  • frame-ancestors 'self': only your own site may frame the page.

Avoid 'unsafe-inline' in script-src. It allows any inline script, injected ones included, which removes most of CSP's protection. Our headers tool flags it unless a nonce or 'strict-dynamic' is present. MDN's CSP guide covers nonces, hashes and every directive.

Framing protection: frame-ancestors or X-Frame-Options

If other sites can show your page in an invisible frame, they can trick visitors into clicking buttons they can't see (clickjacking). The modern control is CSP's frame-ancestors; the older header still works everywhere:

X-Frame-Options: DENY

OWASP's advice is to use frame-ancestors "if possible" and not allow the page in a frame. Use SAMEORIGIN instead of DENY if your own site frames its pages. Sending both is fine; browsers that support frame-ancestors use it.

X-Content-Type-Options

X-Content-Type-Options: nosniff

This tells browsers to trust the Content-Type you send instead of guessing from the content. Without it, a file uploaded as an image could be sniffed and run as a script or HTML. There's only one valid value. OWASP adds the obvious companion: set Content-Type correctly everywhere.

Referrer-Policy

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

When someone follows a link from your site, the browser can send the full URL they came from, which may include search terms, IDs or tokens in the query string. This policy sends the full URL within your own site, only the origin (https://example.com/) to other sites, and nothing when going from HTTPS to HTTP. OWASP notes that modern browsers already default to it, but recommends setting the header explicitly rather than relying on the default.

Permissions-Policy

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

This switches off browser features your site doesn't use, for the page and for anything it embeds. If your site never uses the camera, injected code can't use it either. An empty list () disables a feature; (self) allows it for your own origin only. MDN's Permissions-Policy reference lists the features.

Cross-Origin-Opener-Policy

Cross-Origin-Opener-Policy: same-origin

COOP stops pages on other sites that your page opened, or that opened it, from keeping a handle to your window. It protects against cross-window attacks and is one of the headers needed for features that require cross-origin isolation. If your site relies on pop-ups from other sites, such as payment or sign-in windows, use same-origin-allow-popups.

Don't forget cookies

Session cookies carry their own security settings in the Set-Cookie header:

Set-Cookie: session=…; Secure; HttpOnly; SameSite=Lax; Path=/
  • Secure: sent only over HTTPS.
  • HttpOnly: not readable from JavaScript, so a script injection can't steal it.
  • SameSite=Lax: not sent on most cross-site requests, which blunts cross-site request forgery.

The HTTP Headers Viewer lists each cookie a page sets and names any of these three that are missing.

Headers to drop or leave out

  • X-XSS-Protection. It controlled an old browser filter that's been removed from modern browsers and could itself be abused. OWASP says: "Do not set this header or explicitly turn it off," i.e. X-XSS-Protection: 0. Use CSP instead.
  • Version numbers in Server and X-Powered-By. Server: nginx/1.24.0 or X-Powered-By: PHP/8.3.1 tells attackers which known bugs to try. OWASP recommends removing them or using non-informative values, while noting "attackers have other means of fingerprinting."
  • Public-Key-Pins (HPKP). Deprecated and removed from browsers. A bad pin could lock users out for months.
  • Expect-CT. Obsolete; certificate transparency is now enforced by browsers anyway.

Rolling out CSP without breaking your site

A strict CSP will block anything you forgot: an analytics snippet, an embedded video, an inline onclick. So test it first in report-only mode. The Content-Security-Policy-Report-Only header applies the same policy but only reports violations instead of blocking them.

  1. Send your policy as Content-Security-Policy-Report-Only.
  2. Browse the whole site with the developer console open. Each violation shows which resource and which directive.
  3. Allow what you really need, move inline scripts into files or give them a nonce, and remove what you don't.
  4. When the console stays clean, switch to the real Content-Security-Policy header.

Common mistakes

  • The same header sent twice. A CDN, the web server and the application can each add a header, sometimes with different values. Browsers handle duplicates in different ways, so you may not get the policy you think. Set each header in one place and check the final response.
  • Headers only on some responses. Error pages, redirects and static files often skip the application's headers. In nginx, add_header lines aren't inherited by a location block that has its own add_header, and they're left off error responses unless you add always.
  • HSTS before HTTPS works everywhere. Especially with includeSubDomains.
  • A CSP so loose it does nothing, such as script-src * 'unsafe-inline'.

Headers also control search engines: X-Robots-Tag can keep files like PDFs out of search results, as explained in robots.txt vs noindex.

FAQ

Which security headers are the most important?

For most sites: HSTS, a Content-Security-Policy, framing protection (frame-ancestors or X-Frame-Options) and X-Content-Type-Options: nosniff. Referrer-Policy and Permissions-Policy are quick wins after that.

Do security headers fix vulnerabilities in my code?

No. They limit the damage when something goes wrong, for example by blocking an injected script. You still need to fix the bug that let the script in. Check your headers regularly, but don't treat a perfect score as proof of a secure site.

Can I set security headers in a meta tag?

Only some. A CSP can be set with <meta http-equiv="Content-Security-Policy">, but frame-ancestors and reporting don't work there, and HSTS, framing headers and the rest must be real HTTP headers.

Will HSTS break my site?

Only if some part of the site doesn't work over HTTPS. Start with a short max-age, such as a day, confirm everything works, then raise it.