Article

CSP frame-ancestors: The Modern Clickjacking Defense

CSP frame-ancestors is a Content Security Policy directive that controls which origins may embed your page in an iframe. It is the modern replacement for X-Frame-Options and stops clickjacking. Set frame-ancestors 'none' so no other site can frame you.

By Paul Rudenko, Security ResearcherUpdated Jul 1, 20267 min read

frame-ancestors is a Content Security Policy directive that controls which sites are allowed to embed your page inside a <frame>, <iframe>, <object> or <embed>. It is the modern replacement for the older X-Frame-Options header and the standards-based way to defend against clickjacking. Setting frame-ancestors 'none' or frame-ancestors 'self' stops other sites from framing you at all.

What frame-ancestors does

Clickjacking works by loading your real page inside an invisible iframe on an attacker's site, then tricking a logged-in user into clicking something they can't see — a “delete account” button, a payment confirmation, a permission grant. The browser sends the click to your framed page with the user's real session, so it succeeds. The defence is simple: tell the browser who is allowed to frame you, and refuse everyone else.

frame-ancestors is a directive inside the Content-Security-Policy response header. It is enforced by the browser before it even renders your page in a frame: if the embedding (parent) origin isn't on your allow list, the browser blocks the frame from loading. Because it lives in CSP, it accepts a full source list — you can name specific trusted origins, not just an all-or-one choice.

Syntax and values

The directive takes a space-separated list of sources. The common shapes are:

# Nobody may frame this page — the strongest, most common choice
Content-Security-Policy: frame-ancestors 'none'

# Only your own origin may frame it (fine for same-site widgets/layouts)
Content-Security-Policy: frame-ancestors 'self'

# Your origin plus a named list of trusted partner origins
Content-Security-Policy: frame-ancestors 'self' https://partner.example.com https://*.trusted.dev
  • 'none' — no origin may embed the page. Equivalent in intent to X-Frame-Options: DENY. Use this for pages that should never be framed, which is most of them (login, dashboards, account settings).
  • 'self' — only pages served from the same origin may frame it. Roughly equivalent to X-Frame-Options: SAMEORIGIN.
  • An explicit origin list — e.g. https://app.example.com https://*.partner.com. This is the piece X-Frame-Options could never do: allow a specific set of external embedders.

Note the quotes: 'none' and 'self' are keywords and must be single-quoted, while concrete origins are written bare. See the MDN reference for CSP: frame-ancestors for the complete source-expression grammar.

How it supersedes X-Frame-Options

X-Frame-Options (XFO) was the first anti-framing header. It works, and browsers still honour it, but it is limited: it only understands DENY and SAMEORIGIN. There is no wildcard and no way to list several trusted origins — the old ALLOW-FROM value took a single origin and was never widely or consistently supported, so it is effectively dead. If you need “my site plus these two partners can frame me,” XFO simply cannot express it.

frame-ancestors replaces all of that with a proper source list, and the spec is explicit about precedence: when a page delivers both, the CSP frame-ancestors directive takes precedence and the browser ignores X-Frame-Options entirely. So the modern guidance from the OWASP Clickjacking Defense Cheat Sheet is to set frame-ancestors as your primary control, and optionally keep X-Frame-Options as a belt-and-braces fallback only for very old clients — knowing that any modern browser will use frame-ancestors and ignore the older header.

Common gotcha. frame-ancestors is not covered by default-src. Most CSP directives fall back to default-src when omitted, but this one does not — if you don't write frame-ancestors explicitly, there is no framing protection from CSP no matter how strict your default-src is. Two more traps: X-Frame-Options can only name a single origin and has no wildcard, so it can't match the flexibility of a source list; and if you're unsure a policy is safe to enforce, ship it with Content-Security-Policy-Report-Only first to collect violation reports without breaking legitimate embeds.

For the site owner and for the developer

For the site owner (plain English)

If you run a normal website and no one is supposed to embed your pages inside another site, you want the browser to refuse all framing. The fix is one HTTP header — Content-Security-Policy: frame-ancestors 'none' — and it's the same header a scanner checks for when it reports “missing clickjacking protection” or “no frame-ancestors / X-Frame-Options.” You don't need to touch your page content; your host, CDN, or developer adds it at the server or edge. If you're not sure whether your site is protected, the quickest answer is to scan it and look at the response headers.

Not sure if other sites can iframe your pages? Scan your headers — free and passive — to see whether frame-ancestors (or X-Frame-Options) is set.

Check your security headers

For the developer (exact syntax)

Send the header on every HTML response. The raw header is:

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

Server and platform snippets:

# nginx — add to the server / location block
add_header Content-Security-Policy "frame-ancestors 'none'" always;
# Apache (.htaccess or vhost, mod_headers)
Header always set Content-Security-Policy "frame-ancestors 'none'"
// Cloudflare — Transform Rule (Modify Response Header → Set static)
// Header name:  Content-Security-Policy
// Value:        frame-ancestors 'none'

// Cloudflare Workers — set it on the response instead
export default {
  async fetch(request, env, ctx) {
    const res = await fetch(request);
    const out = new Response(res.body, res);
    out.headers.set("Content-Security-Policy", "frame-ancestors 'none'");
    return out;
  },
};
// Next.js — next.config.js / next.config.mjs
const nextConfig = {
  async headers() {
    return [
      {
        source: "/:path*",
        headers: [
          { key: "Content-Security-Policy", value: "frame-ancestors 'none'" },
        ],
      },
    ];
  },
};

module.exports = nextConfig;

If you already send a larger CSP, add frame-ancestors as one directive among the others — separate directives with semicolons, for example default-src 'self'; frame-ancestors 'none'. And if some pages do need to be embedded by a known partner, swap 'none' for a source list like frame-ancestors 'self' https://partner.example.com rather than dropping the directive.

frame-ancestors is the framing control inside a broader Content Security Policy. It is the modern successor to the header covered in clickjacking and X-Frame-Options, and it's one of the core headers in the HTTP security headers guide.

Frequently asked questions

What is the content security policy frame-ancestors directive?

frame-ancestors is a Content Security Policy directive that tells the browser which origins are allowed to embed your page inside a frame, iframe, object or embed element. It is the standards-based defence against clickjacking, where an attacker loads your real page in a hidden iframe and tricks a logged-in user into clicking it. You deliver it in the Content-Security-Policy response header, and it accepts a source list: 'none' blocks all framing, 'self' allows only your own origin, and you can name specific trusted origins such as https://partner.example.com. The browser enforces it before rendering your page in a frame, so an embed from a disallowed origin never loads. For most sites the right value is frame-ancestors 'none', which prevents any other site from iframing your pages.

frame-ancestors vs X-Frame-Options: what is the difference?

Both defend against clickjacking, but frame-ancestors is the modern, more capable one. X-Frame-Options is an older header that only understands DENY and SAMEORIGIN; its ALLOW-FROM value accepted a single origin and was never widely supported, so it cannot express a list of trusted embedders or use wildcards. CSP frame-ancestors takes a full source list, so you can allow your own origin plus specific partner origins. Crucially, the spec says that when a page sends both headers, the browser uses frame-ancestors and ignores X-Frame-Options entirely — frame-ancestors takes precedence. The practical guidance is to set frame-ancestors as your primary control and treat X-Frame-Options as an optional fallback for very old clients.

How do I stop my site being iframed by other sites?

Send a Content Security Policy header with the frame-ancestors directive on every HTML response. To block all framing use Content-Security-Policy: frame-ancestors 'none'; to allow only your own pages use frame-ancestors 'self'; to allow specific partners list them, for example frame-ancestors 'self' https://partner.example.com. Add it at your server or CDN: nginx uses add_header, Apache uses Header always set, Cloudflare uses a Transform Rule or a Worker, and Next.js uses the headers() function in next.config. You do not need to change page content. One gotcha: frame-ancestors is not covered by default-src, so you must write it explicitly even if you already have a strict CSP, or there is no framing protection.

Does frame-ancestors replace X-Frame-Options?

Yes, for every modern browser. frame-ancestors is the CSP replacement for X-Frame-Options, and where both headers are present the browser honours frame-ancestors and ignores X-Frame-Options. That means setting frame-ancestors is sufficient for current browsers, and it is more flexible because it accepts a source list rather than a single-origin choice. You can still send X-Frame-Options alongside it as a belt-and-braces fallback for legacy clients that predate CSP frame-ancestors support, but it is optional — the two will not conflict because the precedence rule is well defined. If you send only one header, make it frame-ancestors.

Related guides

See your whole external attack surface

One page is a start. The full external scan covers TLS, headers, DNS, exposed files, open services and known-exploited CVEs across your whole domain.

See the full scan →