← Back to blog

Security Headers Checklist: What to Set and in What Order

October 23, 2026
Security Headers Checklist: What to Set and in What Order

A security headers checklist comes down to six response headers: Strict-Transport-Security, Content-Security-Policy, X-Content-Type-Options, Referrer-Policy, a clickjacking control (CSP frame-ancestors) and Permissions-Policy. Set them on the server, not in HTML. Roll out the simple ones first and test the strict ones in report-only mode. This page gives you the values, the order and the checks to confirm each header actually reaches the browser.

Pingfloat
Keep Website Security in View
Pingfloat offers SSL certificate checks to help you review a key part of website security alongside your response-header work.
Check SSL certificates

TL;DR

  • Start with HTTPS and HSTS, then X-Content-Type-Options: nosniff, then clickjacking protection, then Referrer-Policy. Leave full CSP for last because it is the one most likely to break pages.
  • Prefer frame-ancestors in CSP over X-Frame-Options. Sending both is fine if you need to cover very old browsers.
  • Set headers as real HTTP response headers. Meta tags are the weaker option and some policies behave unreliably there.
  • Test CSP with Content-Security-Policy-Report-Only before you enforce it.
  • Verify the live response after every change, on every route type: HTML pages, static assets, redirects and API endpoints.

Table of Contents

What security headers do, and what they do not

Security headers are instructions your server sends with each response. They tell the browser how strictly to treat the page: whether to insist on HTTPS, which scripts may run, whether the page may be framed, and how much URL information to leak to other sites.

They are a defense-in-depth layer, not a repair for bad code. OWASP's guidance is clear that a Content Security Policy makes cross-site scripting harder to exploit but does not fix the underlying vulnerability, so secure coding stays the primary defense. Treat the checklist below as the second wall, not the first.

The order below follows the approach in MDN's practical security implementation guides: begin with TLS and HSTS, move on to clickjacking prevention, then tackle complex policies like CSP. Starting with the low-risk headers means each step gives you protection without a late-night rollback.

The checklist at a glance

PriorityHeaderRecommended starting valueBreakage risk
1Strict-Transport-Securitymax-age=63072000; includeSubDomains; preload (after testing)Medium: affects every subdomain
2X-Content-Type-OptionsnosniffLow, if MIME types are correct
3Content-Security-Policy: frame-ancestorsframe-ancestors 'none' or 'self'Low to medium: breaks legitimate embeds
4Referrer-Policystrict-origin-when-cross-originLow
5Permissions-PolicyDisable features you do not useLow
6Content-Security-Policy (full)Start in report-only modeHigh

Below, each row in detail.

1. Strict-Transport-Security (HSTS)

HSTS tells browsers to refuse plain HTTP for your domain for a set time. It stops downgrade attacks and removes the insecure first hop that a redirect from http:// leaves open.

MDN recommends that all websites implement it, and the suggested configuration is Strict-Transport-Security: max-age=63072000; includeSubDomains; preload. The value 63072000 is two years in seconds.

Before you copy that line, read the caveats:

  • HSTS only works once HTTPS works. Confirm your certificate is valid, complete and not close to expiry. Our SSL Checker shows the chain and expiry date for any hostname, and our piece on TLS versions explains which protocol versions to leave enabled.
  • Fix mixed content first. If pages still load HTTP images or scripts, a strict transport policy turns small warnings into broken pages. Our guide to fixing mixed content walks through finding them.
  • Ramp up the max-age. Begin with a short value such as a day or a week, confirm nothing breaks on any subdomain, then raise it.
  • Treat preload as close to permanent. OWASP's HSTS cheat sheet warns that sending the preload directive can have permanent consequences, because browsers ship the preload list and removal is slow. Only add it when every subdomain will serve HTTPS for the long term.

Send HSTS only over HTTPS responses. Browsers ignore it on plain HTTP.

2. X-Content-Type-Options: nosniff

This is the easiest win on the list. Without it, browsers may guess a file's type from its contents and treat, say, an uploaded text file as a script. With nosniff, the browser trusts the Content-Type you send.

MDN's MIME type guide says all sites must set the header to nosniff and serve correct MIME types. It also notes that the browser then blocks a request when the destination is a style or script and the type does not match.

That last point is where the risk hides. If your server labels a JavaScript file text/plain, or a stylesheet application/octet-stream, nosniff will stop it loading. Before enabling:

  1. Request your main CSS and JS files and read the Content-Type in the response.
  2. Check files served from object storage or a CDN, which often default to generic types.
  3. Check user-uploaded content, which is the case this header exists to protect.

The value is always the same, with no variations: X-Content-Type-Options: nosniff.

3. Clickjacking: frame-ancestors vs X-Frame-Options

Clickjacking hides your page in a transparent frame on another site and tricks users into clicking. The defense is to control who may embed you.

Two mechanisms exist. X-Frame-Options accepts DENY or SAMEORIGIN. The CSP frame-ancestors directive does the same and more, because it can name specific allowed origins. MDN's CSP guide and its page on X-Frame-Options both point to frame-ancestors as the option with more comprehensive control.

X-Frame-OptionsCSP frame-ancestors
Allowed valuesDENY, SAMEORIGIN'none', 'self', or a list of origins
Allow one trusted partnerNo (the ALLOW-FROM form is obsolete)Yes
StatusLegacy, widely supportedCurrent standard
If both are sentIgnored by modern browsersTakes priority

The practical answer for most sites: send Content-Security-Policy: frame-ancestors 'none' if nothing should embed you, or 'self' if your own pages frame each other. Add X-Frame-Options: DENY or SAMEORIGIN alongside only if you must support old clients. Chrome's Lighthouse has an audit that checks for either control, so a site that sets one will pass.

Two cautions. First, frame-ancestors does not work in a <meta> tag, so it must be a header. Second, if you run a checkout widget, status page or embeddable form meant to live in other people's pages, list those origins explicitly rather than using 'none'. For the full set of defenses, see OWASP's clickjacking cheat sheet.

4. Referrer-Policy

When a user clicks a link or your page loads a resource from another site, the browser may send a Referer header with the originating URL. If your URLs contain reset tokens, internal IDs or search terms, that is a leak.

Set Referrer-Policy: strict-origin-when-cross-origin. MDN documents it as the current default in modern browsers: the full URL goes only to same-origin requests, cross-origin requests get only the origin, and nothing is sent when the request drops from HTTPS to HTTP. It replaced the older default of no-referrer-when-downgrade.

Why set it if it is already the default? Because explicit beats implicit. Older browsers, embedded webviews and privacy audits do not all share the same default, and a visible header proves the decision was deliberate. Pick no-referrer for pages with sensitive URLs, accepting that your own analytics lose internal referral data.

Never put secrets in URLs to begin with. The header limits the spread, but the safe design keeps tokens in POST bodies or headers.

4. Referrer-Policy — overview diagram

5. Permissions-Policy

Permissions-Policy lets you switch off browser features such as camera, microphone and geolocation for your pages and for any iframes inside them. MDN's reference page describes it as a mechanism to allow or deny those features, and also flags that it is not Baseline because some widely used browsers do not support it. That makes it a cheap hardening step, not a guarantee.

A typical starting point for a site that uses none of them:

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

If a page does use one, for example a store locator with geolocation, allow it for your own origin only: geolocation=(self). Review the list when you add third-party embeds, because ads and video players are the usual consumers of these features.

6. Content-Security-Policy, the big one

CSP is the strongest header on the list and the one that breaks sites. It lists the sources from which scripts, styles, images, frames and connections may load. Anything else is blocked.

A deliberately strict skeleton to begin from:

Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'

Most real sites will not survive that on day one. Analytics tags, payment scripts, fonts and inline snippets will all be blocked. Roll out like this:

  1. Ship it as report-only. Send Content-Security-Policy-Report-Only with your draft policy. OWASP's testing guide recommends this to catch blocked resources before enforcement, because misconfigured headers can break functionality.
  2. Collect violations for a week or two across real traffic, not only your own browser.
  3. Replace inline scripts with external files, or use nonces or hashes, rather than allowing 'unsafe-inline', which defeats most of the point.
  4. Enforce, then keep a report endpoint running so a new third-party tag does not silently break.

For teams going further, Chrome's engineers describe how Trusted Types, enforced through CSP, helps prevent DOM-based XSS by requiring data to be processed before it reaches dangerous sinks such as innerHTML.

Set headers on the server, not in HTML

It is tempting to paste a <meta http-equiv> tag when you cannot reach server config. OWASP's testing guide advises against it: use HTTP response headers where you can, because meta-based enforcement can be ignored or overridden. MDN's CSP guide says the same in a gentler way, and some directives, frame-ancestors among them, do not work in meta tags at all.

Where the header gets set depends on your stack:

  • Web server: add_header ... always; in nginx, Header always set in Apache.
  • Framework middleware: useful when headers vary per route, such as CSP nonces.
  • CDN or edge: one rule covers everything, but make sure it does not overwrite more specific headers from the origin.
  • Static hosting: most hosts accept a headers file or config block.

If your front end is a headless CMS, headers usually live in the framework or CDN layer instead of the CMS itself. This technical checklist for headless CMS SEO covers neighboring checks to run on that kind of setup.

Do not forget APIs and error responses

Headers matter on JSON endpoints too. MDN's guidance for data-only endpoints is Content-Security-Policy: default-src 'none'; frame-ancestors 'none' plus HSTS, which guards against UI-based attacks even if the client never renders HTML. Observatory's FAQ notes that the headers it expects should not harm API behavior when clients support them.

Also check the responses people forget:

  • Redirects. A 301 from http:// to https:// should happen in one hop. Our guide to testing redirects shows how to inspect each step.
  • Error pages. A 500 or 502 generated by a proxy often comes without any of your headers, because the application never ran.
  • Static assets. CDN-served files can lack the headers your app sets.

How we think about this

Most header problems we see in the wild are not exotic. They are inconsistent. The homepage has HSTS, the subdomain does not. The app sends a clean policy, then the CDN strips it, or an error page from the proxy has none. A checklist run once at launch gives a false sense of safety, because a deploy three months later quietly changes the response.

So our advice is to make header checks a habit, not a project. Inspect the actual response, not the config file you believe is deployed. Check more than one URL type. Re-check after infrastructure changes. And treat the strictest headers as a rollout with a feedback loop, not a switch.

We also think free, no-install tools matter here. A header check should take ten seconds, so people actually do it before a release and not only during an audit.

— Andreas

Verify what the browser actually receives

The fastest check is to read the live response. Our free Response Headers Inspector lets you enter a URL and see the headers returned, so you can confirm HSTS, nosniff, your CSP and Referrer-Policy are present on the pages you care about. If you want to script it, the Pingfloat API is available, and the Website Uptime Monitor can watch the site for outages after you tighten the config. For a wider view of what to review alongside headers, see our web security checklist and the explainer on HTTP headers.

From the command line, curl -sI https://example.com prints the headers. In the browser, open DevTools, go to the Network tab, select the document request and read Response Headers. Check three things each time:

  1. The header is present on the HTML response and on a static asset.
  2. The value matches what you intended, with no duplicated or conflicting lines.
  3. The console shows no CSP violation or blocked MIME type.

A safe rollout plan

  1. Day 1: confirm HTTPS everywhere and fix mixed content. Add X-Content-Type-Options: nosniff and Referrer-Policy.
  2. Week 1: add HSTS with a short max-age. Add frame-ancestors after listing legitimate embedders.
  3. Week 2: add Permissions-Policy and begin CSP in report-only mode.
  4. Weeks 3 to 6: clean up CSP violations, then enforce.
  5. Later: raise HSTS max-age to two years. Add preload only if every subdomain is ready.
  6. Ongoing: re-check after every CDN, proxy or framework change.

FAQ

Should I use X-Frame-Options or CSP frame-ancestors?

Use frame-ancestors as your main control, since it can name specific allowed origins and takes priority when both headers are present in modern browsers. Keep X-Frame-Options only as a fallback for very old clients. If your pages should never be embedded, frame-ancestors 'none' is the simplest choice.

Is Referrer-Policy needed if browsers already default to strict-origin-when-cross-origin?

Setting it is still worthwhile. The explicit header makes behavior consistent across older browsers and webviews, and it documents your intent for auditors. If your URLs can contain sensitive data, consider a stricter value such as no-referrer.

Does X-Content-Type-Options: nosniff break anything?

It can, but only when files are served with the wrong Content-Type. A script labeled as plain text, or a stylesheet labeled as a generic binary, will be blocked. Check the MIME types of your scripts and styles before enabling it, and the header is safe to keep on.

Can I set security headers with meta tags?

Avoid it. HTTP response headers are the authoritative method, and meta-based enforcement can be ignored or overridden. Some directives, including frame-ancestors, are not supported in meta tags at all.

Do APIs need security headers?

Yes, a small set. Data-only endpoints benefit from HSTS, nosniff and a locked-down CSP such as default-src 'none'; frame-ancestors 'none'. These protect against UI-based attacks even if a client never renders the response.

Sources