# Security Headers Checklist: 6 HTTP Headers Every Site Should Send

Security headers are the cheapest hardening you'll ever ship: a few lines of server config, no code changes, and whole classes of attack — clickjacking, MIME sniffing, protocol downgrades, much of XSS — get harder or impossible. Here are the six worth sending on every site, in the order that's safest to roll them out.

## The checklist

| Header | Recommended value | Stops |
|---|---|---|
| [X-Content-Type-Options](/headers/x-content-type-options) | `nosniff` | Files being run as a type they weren't served as |
| [Referrer-Policy](/headers/referrer-policy) | `strict-origin-when-cross-origin` | Full URLs leaking to other sites |
| [X-Frame-Options](/headers/x-frame-options) | `DENY` (or CSP `frame-ancestors 'none'`) | Clickjacking via iframes |
| [Permissions-Policy](/headers/permissions-policy) | `camera=(), microphone=(), geolocation=()` | Injected code asking for powerful features |
| [Strict-Transport-Security](/headers/strict-transport-security) | `max-age=31536000; includeSubDomains` | HTTPS → HTTP downgrades |
| [Content-Security-Policy](/headers/content-security-policy) | depends on your site — start in report-only mode | Most XSS |

The first four are nearly risk-free: they rarely break anything. HSTS and CSP need care, so they come last. To build all six at once for your server or host, use the [security headers generator](/tools/security/security-headers-generator).

## 1–4: the safe ones

**`X-Content-Type-Options: nosniff`** makes browsers trust your `Content-Type` instead of guessing. The only thing it can break is a script or stylesheet served with the wrong type, which you want to fix anyway.

**`Referrer-Policy: strict-origin-when-cross-origin`** is already the default in current browsers; sending it makes the behaviour explicit for older ones. Use `no-referrer` or `same-origin` if your URLs carry anything sensitive.

**`X-Frame-Options: DENY`** stops other sites from loading your pages in an iframe. Use `SAMEORIGIN` if you frame your own pages. Once you have a CSP, its `frame-ancestors` directive does the same job and is the modern replacement — send both for older browsers.

**`Permissions-Policy`** switches off browser features you don't use. List the ones that matter — camera, microphone, geolocation, payment — with an empty allowlist `()`.

## 5: HSTS, carefully

`Strict-Transport-Security` tells browsers to use HTTPS for your domain, every time, for `max-age` seconds — even if someone types `http://`. It's powerful precisely because it can't be undone quickly: if a subdomain doesn't support HTTPS and you've sent `includeSubDomains`, browsers will refuse to load it until the max-age runs out.

Roll it out in steps:

1. `max-age=300` — five minutes, to test.
2. `max-age=86400; includeSubDomains` — once you've confirmed every subdomain serves HTTPS.
3. `max-age=31536000; includeSubDomains` — a year, the usual production value.
4. Add `preload` and submit the domain to hstspreload.org only if you're sure; removal takes months.

## 6: CSP, in report-only mode first

A Content-Security-Policy tells the browser which sources may provide scripts, styles, images and frames. A good one turns most injected-script XSS into a blocked request. It's also the header most likely to break your site, so start with `Content-Security-Policy-Report-Only`: the browser reports violations to the console (and to an endpoint, if you configure one) without blocking anything.

A starting policy for a site that serves its own assets:

```http
Content-Security-Policy: default-src 'self'; img-src 'self' data:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'
```

Third-party scripts, analytics and inline `<script>` blocks all need explicit allowances — the [CSP guide](/blog/content-security-policy-guide) covers nonces, hashes and the usual breakages.

## Setting them

**Next.js** (`next.config.js`):

```js
const securityHeaders = [
  { key: "X-Content-Type-Options", value: "nosniff" },
  { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
  { key: "X-Frame-Options", value: "DENY" },
  { key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=()" },
  { key: "Strict-Transport-Security", value: "max-age=31536000; includeSubDomains" },
];

module.exports = {
  async headers() {
    return [{ source: "/(.*)", headers: securityHeaders }];
  },
};
```

**nginx** (inside the HTTPS `server` block — `always` adds them to error pages too):

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

One nginx trap: `add_header` directives are inherited only if the inner block defines none of its own. A single `add_header` inside a `location` silently drops every header set at the `server` level for that location.

**Express**: the [Helmet](https://helmetjs.github.io/) middleware sets sensible defaults for all of these, including a starter CSP — `app.use(helmet())`.

**Vercel** (`vercel.json`) and **Netlify** (`_headers`):

```json
{
  "headers": [
    {
      "source": "/(.*)",
      "headers": [{ "key": "X-Content-Type-Options", "value": "nosniff" }]
    }
  ]
}
```

```text
/*
  X-Content-Type-Options: nosniff
  Referrer-Policy: strict-origin-when-cross-origin
```

## Headers you can drop

- **X-XSS-Protection** — the browser XSS filters it controlled have been removed. Send `0` or nothing.
- **Public-Key-Pins (HPKP)** — removed from browsers; it could lock users out of a site permanently.
- **Expect-CT** — obsolete now that Certificate Transparency is enforced by default.
- **Feature-Policy** — replaced by Permissions-Policy.

## Check your work

```bash
curl -sI https://your-site.example | grep -iE "strict-transport|content-security|x-content-type|referrer-policy|permissions-policy|x-frame"
```

To check many URLs at once, paste them into the [HTTP Status Checker](/tools/web/http-status-checker). For every header's full set of values, see the [HTTP headers reference](/headers).
