← Back to ClickJack Test

Cloudflare Clickjacking Protection

Setting X-Frame-Options and CSP frame-ancestors on Cloudflare.

Which method should you use?

Cloudflare gives you three ways to add response headers. Pick based on your setup:

  • Managed Transforms -- one click, no configuration. Adds x-frame-options: SAMEORIGIN plus four other security headers. Good if you want a quick baseline and SAMEORIGIN fits your needs. Available on all plans.
  • Response Header Transform Rules -- the method you will use most often. Set any header with any value. Apply to all requests or filter by URL. Available on all plans (10 rules on Free, 25 on Pro).
  • Pages _headers file -- for static sites deployed on Cloudflare Pages. Define headers per-route in a plain text file in your build output.
Do not use Page Rules. Cloudflare deprecated Page Rules in January 2025 and plans to migrate them automatically. New accounts can no longer create them. Response Header Transform Rules are the replacement.

Method 1: Managed Transforms (one click)

Cloudflare ships a Managed Transform called Add security headers. Enable it and Cloudflare adds these headers to every response:

x-content-type-options: nosniff
x-xss-protection: 1; mode=block
x-frame-options: SAMEORIGIN
referrer-policy: same-origin
expect-ct: max-age=86400, enforce

Steps:

  1. Open the Cloudflare dashboard and select your domain.
  2. Go to Security > Settings.
  3. Under Managed Transforms, toggle on Add security headers.

The header takes effect immediately. Run your site through ClickJack Test to confirm.

Managed Transforms uses SAMEORIGIN, not DENY. If you never embed your own pages in iframes, you probably want DENY. Use a Transform Rule instead (Method 2) for that.

Also worth noting: the Managed Transform adds x-xss-protection, which sets a header that was removed from all modern browsers years ago. It does no harm, but you can see why a custom Transform Rule gives you tighter control.

Method 2: Response Header Transform Rules

This is the recommended approach. You set exactly the headers and values you need, and you can apply them to all requests or filter by URL pattern. Available on all plans.

Dashboard setup

  1. Open the Cloudflare dashboard and select your domain.
  2. Go to Rules > Transform Rules.
  3. Click Create rule > Response Header Transform Rule.
  4. Give the rule a descriptive name, e.g. “Clickjacking protection”.
  5. Under When incoming requests match, choose All incoming requests (or define a custom filter expression if you only want headers on certain paths).
  6. For Modify response header, select Set static. Enter:
    • Header name: Content-Security-Policy
    • Value: frame-ancestors 'none'
  7. Click Set new header. Add a second header for X-Frame-Options. Select Set static. Enter:
    • Header name: X-Frame-Options
    • Value: DENY
  8. Click Deploy.

One header is enough. Both gives defense in depth: CSP takes precedence in modern browsers, X-Frame-Options covers older ones. If you embed your own pages in iframes, use 'self' and SAMEORIGIN instead.

Transform Rules apply at the Cloudflare edge before the response reaches the browser. They do not modify the response your origin server sends -- they add to it. This means you can add clickjacking headers without touching your backend code, which is the whole point of using Cloudflare for this.

Transform Rules also apply to Cloudflare error pages and Custom Errors -- so even if your origin is down, Cloudflare still adds the header to the error page it serves.

If your origin already sets these headers

If your origin server already responds with X-Frame-Options or CSP, a Transform Rule using Set static will overwrite the origin's value. If you want to keep the origin's header and add your own, use Add static instead -- but this results in duplicate headers, which is sloppy. Better to pick one place (origin or Cloudflare) and stick with it.

API setup

If you manage Cloudflare via API, here is the equivalent rule. You need a token with the Transform Rules: Edit permission.

curl "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets/$RULESET_ID" \
  --request PUT \
  --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
  --json '{
    "rules": [
      {
        "description": "Clickjacking protection",
        "expression": "true",
        "action": "rewrite",
        "action_parameters": {
          "headers": {
            "Content-Security-Policy": {
              "operation": "set",
              "value": "frame-ancestors '\''none'\''"
            },
            "X-Frame-Options": {
              "operation": "set",
              "value": "DENY"
            }
          }
        }
      }
    ]
  }'

If no ruleset exists yet for the http_response_headers_transform phase, create one first using the Create zone ruleset endpoint with kind: zone and phase: http_response_headers_transform. Check the Cloudflare API docs for full details.

Method 3: Cloudflare Pages _headers file

If you deploy on Cloudflare Pages (static sites), you can define headers in a _headers file in your build output directory. This file is not served to visitors -- Cloudflare parses it and applies the rules to your static asset responses.

The file belongs in the public/ or static/ directory (whichever your framework copies to the build output).

# public/_headers
/*
  Content-Security-Policy: frame-ancestors 'none'
  X-Frame-Options: DENY

The rules:

  • First line is the URL pattern. /* matches everything. You can use paths like /app/* for specific routes.
  • Following indented lines are Name: Value header pairs. Indentation matters -- use spaces or tabs consistently.
  • Up to 100 header rules per file. Each line limited to 2,000 characters.
  • If the same header is set by multiple matching rules, values are joined with a comma.

To remove a header set by a broader rule, prefix the header name with !:

/*
  Content-Security-Policy: frame-ancestors 'none'

/embeddable-widget/*
  ! Content-Security-Policy

Important: the _headers file only applies to static assets. If your Pages project uses Functions, those dynamic responses need headers set in the Function code itself.

What about Workers?

If you use Cloudflare Workers, set headers in the Worker response:

export default {
  async fetch(request, env, ctx) {
    const response = await fetch(request);
    const newHeaders = new Headers(response.headers);
    newHeaders.set("Content-Security-Policy", "frame-ancestors 'none'");
    newHeaders.set("X-Frame-Options", "DENY");
    return new Response(response.body, {
      status: response.status,
      statusText: response.statusText,
      headers: newHeaders,
    });
  },
};

Workers is the most flexible option -- you can make header values dynamic based on request data -- but it is overkill for a static header. Transform Rules are simpler and do not consume Worker request quotas.

Verify it is working

After deploying your rule, check that the headers are present:

curl -I https://yoursite.com

Look for X-Frame-Options: DENY or Content-Security-Policy: frame-ancestors 'none' in the output.

Also test a path that would normally return an error -- Transform Rules apply to Cloudflare error pages too:

curl -I https://yoursite.com/nonexistent

Then run it through ClickJack Test to confirm.

Plan limits

PlanActive Transform RulesRegex support
Free10No
Pro25No
Business50Yes
Enterprise300Yes

Source: Cloudflare Transform Rules docs, retrieved June 2025.