[GUIDE]

How to Add Security Headers in Cloudflare: HSTS, CSP, and More

Seen enough? Run a free scan on your own site.Try it now →

If you want the short answer: enable HSTS under SSL/TLS → Edge Certificates, and add the rest of your security headers — Content-Security-Policy, X-Frame-Options (or CSP frame-ancestors), Referrer-Policy, and Permissions-Policy — with Transform Rules → HTTP Response Header Modification, using the "Set static" action. Cloudflare handles X-Content-Type-Options and the Server header for you. Then verify what your deployed site actually returns with a security headers checker, because with Cloudflare in front of your origin, the edge can add, strip, or override a header — and the browser only honors what ships in the live response.

This guide walks through each header, where it lives in Cloudflare, a sane starting value, and how to confirm it actually works at the edge.

Which security headers should Cloudflare send?

Here's the practical set, what each does, and where it's configured in Cloudflare. Each header links to its MDN reference.

HeaderWhat it doesWhere in Cloudflare
Strict-Transport-SecurityForces HTTPS, blocking protocol-downgrade and SSL-strip attacksSSL/TLS → Edge Certificates → HSTS toggle
Content-Security-PolicyControls which sources can load scripts, styles, frames, etc. — your main XSS defenseTransform Rules → Set static
X-Frame-OptionsStops your pages being embedded in frames (clickjacking)Transform Rules → Set static (or use CSP frame-ancestors)
X-Content-Type-OptionsDisables MIME sniffing; the only valid value is nosniffOften set by Cloudflare automatically; otherwise check origin
Referrer-PolicyLimits how much URL data leaks to third partiesTransform Rules → Set static
Permissions-PolicyScopes powerful browser features (camera, mic, geolocation)Transform Rules → Set static

A few notes before you copy values blindly:

  • HSTS preload is hard to undo. Only enable includeSubDomains (and later preload) once every subdomain serves HTTPS. The OWASP Secure Headers Project and the HSTS spec, RFC 6797, both stress that max-age should be long for real protection.
  • CSP is the one header you must tune. A default-src 'self' policy will break any third-party script, analytics, or font CDN you use until you allow it explicitly.
  • frame-ancestors supersedes X-Frame-Options in modern browsers, so a CSP frame-ancestors 'none' is the primary clickjacking control. Keeping X-Frame-Options too is fine for older clients.

How do I enable HSTS in Cloudflare?

HSTS isn't a Transform Rule — Cloudflare gives it a dedicated toggle. In the dashboard, go to SSL/TLS → Edge Certificates and enable HSTS (HTTP Strict Transport Security). Cloudflare's HSTS documentation covers the exact options, which boil down to:

  • Max-Age: set this to 12 months. A short window leaves a downgrade gap, which is the whole thing HSTS exists to close.
  • Include subdomains: enable this only once every subdomain reliably serves HTTPS. If a subdomain still answers on plain HTTP, turning this on will make it unreachable for clients that have seen the header.
  • Preload: this asks browsers to ship your domain in their preloaded HSTS list. Combined with includeSubDomains, it is hard to reverse — browsers cache it for a long time. Enable it last, after you're confident the whole apex and every subdomain is HTTPS-only and will stay that way.

The end result is a response header along the lines of:

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

Because this is set at the edge, it applies to every response Cloudflare serves for the zone — you don't repeat it per route.

How do I add CSP and other headers with Transform Rules?

For everything that isn't HSTS, Cloudflare's canonical mechanism is HTTP Response Header Modification, part of Transform Rules. In the dashboard, go to Rules → Transform Rules → HTTP Response Header Modification, create a rule, choose the "Set static" action, and enter the header name and value. This is documented in the official Cloudflare response header modification reference.

Add one "Set static" entry per header. A solid starting set looks like this:

Header nameValue
Content-Security-Policydefault-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'
X-Frame-OptionsDENY
Referrer-Policystrict-origin-when-cross-origin
Permissions-Policycamera=(), microphone=(), geolocation=()

You can apply each rule to all incoming requests, or scope it with a filter expression (for example, only HTML responses, or only a hostname) if you need different policies for different parts of the zone. Cloudflare evaluates the rule on the response leaving the edge, so the header reaches the browser regardless of whether your origin set one.

A note on Content-Security-Policy: the example above is a strict baseline. default-src 'self' plus script-src 'self' blocks every third-party script — analytics, tag managers, embedded widgets, font CDNs — until you add those origins explicitly. Treat the CSP value as something you iterate on: deploy it, watch the browser console for violations, and add only the origins your app genuinely loads. The directives object-src 'none', base-uri 'self', and frame-ancestors 'none' are safe to keep tight from day one.

What about X-Content-Type-Options and the Server header?

Two headers you often don't need to set manually:

  • X-Content-Type-Options: nosniff — Cloudflare can set this automatically. If a scan reports it missing, it means the edge isn't adding it for this response and your origin isn't either. You can add it with a Transform Rule the same way as the others, or fix it at the origin.
  • Server — Cloudflare replaces the Server header automatically (you'll typically see cloudflare). If you still see your origin's server string (for example nginx or a version banner), that's a sign requests are reaching the client without passing through Cloudflare's edge as expected — check your origin and proxy configuration.

For clickjacking specifically, prefer the CSP frame-ancestors 'none' directive over X-Frame-Options, since modern browsers honor frame-ancestors first. Setting X-Frame-Options: DENY as well via a Transform Rule is a reasonable belt-and-suspenders move for older clients.

Why do my Cloudflare headers and my framework headers disagree?

This is the gotcha that trips people up, and it deserves its own section. With Cloudflare sitting in front of your origin, two layers can both touch headers — and they can disagree.

  • A header you set in Next.js, nginx, or your application framework can be stripped or overridden at the Cloudflare edge.
  • A header you set in a Cloudflare Transform Rule can be the only place that header exists, even though your config files say nothing about it.
  • Cloudflare can add headers (like the Server rewrite or, sometimes, X-Content-Type-Options) that never came from your origin.

The practical consequence: your config files are not the source of truth — the live response is. You can have a perfectly correct next.config.js or nginx block and still ship a weak header set because the edge layer changed something. The only reliable way to know what a visitor's browser receives is to inspect the actual deployed response.

This is also why repeatable, reviewable configuration helps. Cloudflare Transform Rules can be managed through the Cloudflare API or with infrastructure-as-code tools like Terraform, so your header rules live in version control instead of only in the dashboard — useful when you want the same policy across zones or environments. Keep the exact API and Terraform schemas pinned to Cloudflare's current docs rather than copying ad-hoc snippets.

How do I verify my Cloudflare security headers are correct?

Setting headers in the dashboard is half the job. The browser only enforces what your deployed site actually returns, and with the edge in the mix, that can differ from any single config layer.

Quick local check with curl:

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

For a real verdict, run an external security headers checker against your live URL. A black-box scanner like SteelSuit requests your deployed site, inspects the response headers it actually receives, and flags what's missing or weak — it detects Cloudflare from the response, with no source-code access and nothing to install. Concretely, SteelSuit's header check:

  • Flags any missing header from the set above (HSTS, CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy).
  • Acts as an HSTS checker — it parses your max-age and warns when it's shorter than six months, since a brief window leaves a downgrade gap.
  • Runs a CSP evaluator that flags 'unsafe-inline' and 'unsafe-eval' in script-src/default-src, wildcard (*) sources in critical directives, and missing directives like object-src, base-uri, and script-src.

Because it reads the real response, a checker catches exactly the Cloudflare failure mode this guide warned about: the gap between "I set this in the dashboard" (or in my framework) and "the browser actually received this." Config inspection alone can't surface that — only the live response can.

How to fix Cloudflare headers when the scan flags them

If a scan reports missing or weak headers, the fix usually maps back to one of the two layers:

  • Missing header entirely → add a Transform Rule ("Set static") for it, or enable HSTS under Edge Certificates. Re-deploy and re-scan.
  • HSTS too short → raise Max-Age in SSL/TLS → Edge Certificates to at least one year (12 months is a sensible default).
  • CSP allows unsafe-inline or * → tighten the Transform Rule value: drop wildcards, replace 'unsafe-inline' with explicit origins or nonces/hashes generated at the origin.
  • Header set in your framework but not on the live response → the edge stripped or overrode it. Re-add it as a Cloudflare Transform Rule so it's applied at the layer that terminates the request last.
  • Server header still shows your origin → traffic may be bypassing Cloudflare; verify the DNS records are proxied (orange-cloud) and the origin isn't directly reachable.

For a broader pass beyond headers, see our web app security checklist. If you also run a framework behind Cloudflare, our guides on Next.js security headers and nginx security headers cover the origin side of the same setup. And to make sense of what a scan returns, our guide on reading a vulnerability scan report walks through the output.

Security headers are one of the highest-leverage, lowest-effort hardening steps you can take with Cloudflare — one HSTS toggle, a handful of Transform Rules, and one verification scan against the live response, and you've closed a whole class of clickjacking, downgrade, and injection gaps at the edge.

Frequently asked

How do I add security headers in Cloudflare?

For HSTS, go to SSL/TLS → Edge Certificates and enable HSTS. For Content-Security-Policy, X-Frame-Options, Referrer-Policy, and Permissions-Policy, use Rules → Transform Rules → HTTP Response Header Modification, add a rule, choose 'Set static', and enter the header name and value. Cloudflare applies these at the edge to matching responses.

How do I enable HSTS in Cloudflare?

In the Cloudflare dashboard, open SSL/TLS → Edge Certificates and enable HSTS. Set max-age to 12 months and enable includeSubDomains only once every subdomain reliably serves HTTPS. Treat preload with care — combined with includeSubDomains it is hard to reverse, so enable it last.

How do I set a Content-Security-Policy in Cloudflare?

Use Rules → Transform Rules → HTTP Response Header Modification. Create a rule, select 'Set static', set the header name to Content-Security-Policy, and paste your policy value, for example: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'. Tune the value to the origins your app actually loads.

Why do my Cloudflare headers differ from my framework config?

With Cloudflare in front of your origin, the edge and origin can disagree. Cloudflare may add, strip, or override headers, so a header set in Next.js or nginx can be removed at the edge, or vice versa. That is why you verify against the live deployed response, not your config files.

Is X-Frame-Options or CSP frame-ancestors better in Cloudflare?

Prefer CSP frame-ancestors 'none', set via a Transform Rule — it supersedes X-Frame-Options in modern browsers and is more flexible. You can add X-Frame-Options the same way as a belt-and-suspenders fallback for older clients.

How do I verify Cloudflare is sending the right headers?

Run an external security headers checker against your deployed URL, or curl -sI the site and grep for the header names. A checker reads the response Cloudflare actually returns, flags missing or weak headers, parses HSTS max-age, and evaluates your CSP — catching cases where the edge stripped or overrode something.