Security5 min read

The Security Headers Checklist: HSTS, X-Frame-Options and the Rest

Five response headers stop whole classes of attack for the cost of a few lines of config. What each one does, the value to use, how to check your site with curl, and a copy-paste Next.js setup.

By UseShipReady

Every production site should send five security headers: Strict-Transport-Security (forces HTTPS), Content-Security-Policy (limits which scripts can run), X-Frame-Options or CSP frame-ancestors (blocks clickjacking), X-Content-Type-Options: nosniff (stops MIME sniffing), and Referrer-Policy (limits what URLs leak to other sites). Check yours with curl -sI https://yourdomain.com. Any header missing from the output is not being sent.

Security headers are instructions your server sends alongside each page, telling the browser to enforce protections it would otherwise leave off. They do not fix bugs in your code. They shrink what an attacker can do with one, and they cost almost nothing to add. Most frameworks send none of them by default, which is why a freshly deployed AI-built app usually has none.

How do I check which security headers my site sends?

shell
curl -sI https://yourdomain.com | grep -iE 'strict-transport|content-security|x-frame|x-content-type|referrer-policy'

Check a logged-in page as well as the homepage. Headers set by a CDN rule often apply everywhere, but headers set in application code sometimes only reach the routes that code handles.

The checklist

HeaderRecommended valueWhat it prevents
Strict-Transport-Securitymax-age=31536000; includeSubDomainsDowngrade to plain HTTP and cookie theft on hostile networks
Content-Security-PolicySite-specific; start with frame-ancestors and object-src 'none'Injected scripts running (XSS impact), framing
X-Frame-OptionsDENY or SAMEORIGINClickjacking in older browsers
X-Content-Type-OptionsnosniffBrowsers guessing a file is script or HTML
Referrer-Policystrict-origin-when-cross-originFull URLs, including tokens in query strings, leaking to other sites
Recommended security headers and values

Strict-Transport-Security (HSTS)

Redirecting HTTP to HTTPS is not enough on its own: the first request still goes out unencrypted, and someone on the same network can intercept it before the redirect happens. HSTS tells the browser to use HTTPS for your domain for the next max-age seconds without asking. One year (31536000) is the common recommendation. Add includeSubDomains once you are sure every subdomain serves HTTPS, because it applies to all of them.

Be careful with preload. It asks browsers to hardcode HTTPS for your domain, and removal from the preload list takes months. Do not add it until HSTS has run without problems for a while.

Content-Security-Policy

CSP is the most powerful header on this list and the only one that takes real effort, because the right value depends on every script, style and embed your site loads. It gets its own article: the practical guide to Content Security Policy. If you only add one directive today, add frame-ancestors 'self', which is the modern replacement for X-Frame-Options.

X-Frame-Options

Clickjacking loads your site invisibly inside another page and tricks visitors into clicking your buttons, such as "delete account" or "confirm payment", while they think they are clicking something else. DENY blocks all framing; SAMEORIGIN allows only your own pages to frame you. CSP frame-ancestors does the same job with more control and takes precedence where supported, but sending both costs nothing and covers older browsers.

X-Content-Type-Options

Without nosniff, a browser may ignore the declared content type and guess from the bytes. If your app lets users upload files, a file declared as an image but containing script could be treated as script. nosniff has one valid value and no known downsides, which makes it the easiest header on the list.

Referrer-Policy

When a visitor follows a link or your page loads a third-party resource, the browser can send the full URL of the current page. If that URL contains a password-reset token, a session identifier or a private document ID, you have just shared it. strict-origin-when-cross-origin sends only your domain to other sites and the full URL within your own site. Modern browsers already default to it, but sending it explicitly removes the dependency on browser defaults.

Permissions-Policy (optional)

Permissions-Policy switches off browser features your site never uses, such as the camera, microphone and geolocation, so an injected script or a third-party embed cannot request them. A conservative value is camera=(), microphone=(), geolocation=(). It is a useful extra rather than a baseline requirement, which is why it is not in the table above.

Which security header should I add first?

If you are starting from nothing, the order below gets the most protection soonest with the least risk of breaking anything:

  1. X-Content-Type-Options: nosniff and Referrer-Policy. Both are one fixed value with no known downsides, so add them today.
  2. X-Frame-Options: DENY, unless your site is meant to be embedded elsewhere. If it is, use SAMEORIGIN or a CSP frame-ancestors list naming the sites allowed to frame you.
  3. HSTS, starting with a short max-age such as 86400, then raising it to a year once you have confirmed every page loads over HTTPS.
  4. Content-Security-Policy, in Content-Security-Policy-Report-Only mode first, tightened over a week or two as you learn what your pages actually load.

Once they are live, re-run the curl command against a few different routes, including an error page, to confirm the headers apply everywhere and not only to the pages your framework renders.

Which old headers should I not add?

X-XSS-Protection is deprecated. Modern browsers ignore it, and the filter it controlled in older browsers could itself be abused. If you send it at all, send X-XSS-Protection: 0. Expect-CT and Public-Key-Pins are also obsolete and should be removed rather than configured.

How do I add security headers in Next.js?

javascript
// next.config.js
const securityHeaders = [
  { key: 'Strict-Transport-Security', value: 'max-age=31536000; includeSubDomains' },
  { key: 'X-Frame-Options', value: 'DENY' },
  { key: 'X-Content-Type-Options', value: 'nosniff' },
  { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
  { key: 'Content-Security-Policy', value: "frame-ancestors 'none'; object-src 'none'; base-uri 'self'" },
]

module.exports = {
  async headers() {
    return [{ source: '/:path*', headers: securityHeaders }]
  },
}

On Vercel you can set the same list in vercel.json under headers; on Netlify, in a _headers file; behind nginx, with add_header ... always; in the server block. The always matters in nginx: without it, error pages are sent without the headers.

nginx
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

Do security headers affect SEO or performance?

No measurable effect on either. They add a few hundred bytes to each response and have no bearing on ranking. The only header that can break a page is an overly strict Content-Security-Policy, which is why CSP should be rolled out in report-only mode first and the others can go straight to production.

Headers protect the page. The cookie that holds a visitor’s session has its own set of flags, covered in Secure, HttpOnly and SameSite explained.

How UseShipReady checks your headers

UseShipReady reads the headers on responses it has already fetched and reports each missing or weak one separately, with the header it saw as evidence. HSTS is checked for presence, a short max-age and a missing includeSubDomains. The others are a missing Strict-Transport-Security header, a missing or permissive X-Frame-Options, a missing X-Content-Type-Options and a missing Referrer-Policy.

For the task-ordered version of everything worth checking before launch, see the production readiness checklist.

Frequently asked questions

What are the most important HTTP security headers?
Strict-Transport-Security, Content-Security-Policy, X-Frame-Options (or CSP frame-ancestors), X-Content-Type-Options and Referrer-Policy. Together they enforce HTTPS, limit script injection, block clickjacking, stop MIME sniffing and control referrer leakage.
Is X-Frame-Options still needed if I use CSP frame-ancestors?
Modern browsers use frame-ancestors and ignore X-Frame-Options when both are present. Sending both is still sensible because it protects older browsers at no cost.
What HSTS max-age should I use?
One year (31536000 seconds) is the common recommendation. Start shorter, for example a day, if you are unsure every page and subdomain serves HTTPS, then raise it once nothing breaks.
Can security headers break my website?
Content-Security-Policy can, if it blocks a script your site needs, so test it in report-only mode first. The other headers on this checklist rarely cause problems, with one exception: HSTS with includeSubDomains will break any subdomain that only serves HTTP.

Sources

Related reading

Is your site ready to ship?

UseShipReady scans up to ten pages for security, AI exposure, email deliverability, SEO and launch readiness — with a paste-ready fix for each finding. Free, no signup.