# Clickjacking protection when you cannot set a CSP header (/en/blog/frame-ancestors-without-csp-header)



Clickjacking protection needs a real HTTP response header. The Content Security Policy (CSP) directive that controls who can frame your page, [`frame-ancestors`](/en/docs/web-security/policies/content-security-policy/directives/frame-ancestors), is silently dropped when the policy comes from a `<meta>` tag, so a meta-delivered CSP gives you no framing protection at all. If you cannot set headers freely, the host-limited fallback is `X-Frame-Options`, which is simpler and more constrained. This post explains the meta-tag gap, what `X-Frame-Options` can and cannot do, and what to reach for when your only options are limited headers or a meta tag.

## frame-ancestors is dropped in a meta tag [#frame-ancestors-is-dropped-in-a-meta-tag]

`frame-ancestors` decides which origins may embed your page in a frame, which is your defense against clickjacking (an attacker framing your page invisibly and tricking a user into clicking through it). It works when the policy arrives as the `Content-Security-Policy` response header. It does **not** work from a meta tag.

The CSP specification lists `frame-ancestors` among the directives that are ignored when the policy is delivered in a `<meta http-equiv>` element. The browser keeps the rest of the meta policy and quietly discards this directive. There is no console error telling you the page is unprotected, which is what makes it dangerous: the policy looks present, but the framing rule is gone.

```html
<!-- frame-ancestors here is silently ignored, no clickjacking protection -->
<meta http-equiv="Content-Security-Policy" content="frame-ancestors 'none'">
```

This is one of several directives that only work as a header. The full list and the reasoning are in [CSP meta tag vs HTTP header](/en/blog/csp-meta-tags-vs-headers). The takeaway for clickjacking is narrow and absolute: if you can only emit a meta tag, you cannot use `frame-ancestors`, and you need a different mechanism.

## X-Frame-Options, the host-limited fallback [#x-frame-options-the-host-limited-fallback]

When you cannot set a CSP header but can set some response headers, [`X-Frame-Options`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options) is the older header that controls framing. Some hosts and CDNs expose a short list of allowed security headers even when they do not let you set an arbitrary CSP, and `X-Frame-Options` is often on that list. It has two usable values:

```http
X-Frame-Options: DENY
```

```http
X-Frame-Options: SAMEORIGIN
```

`DENY` stops every site, including your own, from framing the page. `SAMEORIGIN` allows only your own origin to frame it. That is the whole vocabulary. The header is coarse on purpose, which is both its limit and the reason it still works as a fallback.

The constraints matter:

* **No allowlist of multiple origins.** `frame-ancestors` can name several trusted embedders (`frame-ancestors 'self' https://partner.example`). `X-Frame-Options` cannot. You get same-origin or nobody, and no way to permit one named partner.
* **`ALLOW-FROM` is deprecated and does not work.** The old `X-Frame-Options: ALLOW-FROM https://partner.example` value was meant to allow a single external embedder. Modern browsers ignore it. Do not rely on it; if you need to allow a specific partner to frame you, that requires `frame-ancestors`, which requires a real header.
* **`frame-ancestors` supersedes it.** When a page sends both, browsers honour the CSP directive and ignore `X-Frame-Options` (see [X-Frame-Options vs frame-ancestors](/en/blog/x-frame-options-vs-frame-ancestors) for the full comparison). So `X-Frame-Options` is the fallback you use only when `frame-ancestors` is not available to you.

For the directive's own behaviour, its syntax, and how it relates to the legacy header in detail, see the [frame-ancestors directive reference](/en/docs/web-security/policies/content-security-policy/directives/frame-ancestors). This post is the practical "what do I do when I can't set the header" version; the reference is the complete one.

## What to do with limited headers or only a meta tag [#what-to-do-with-limited-headers-or-only-a-meta-tag]

Match the fix to what your host actually lets you control.

**You can set arbitrary response headers.** Use `frame-ancestors` on a real `Content-Security-Policy` header and skip `X-Frame-Options` entirely. `frame-ancestors 'none'` to block all framing, `frame-ancestors 'self'` for same-origin only, or a named allowlist for specific partners. This is the modern, expressive option and the one to prefer whenever it is available.

```http
Content-Security-Policy: frame-ancestors 'self' https://partner.example
```

**You can set only a short list of security headers (no CSP).** Use `X-Frame-Options: DENY` if nothing should frame the page, or `SAMEORIGIN` if your own pages frame it. Accept that you cannot allow a named external partner from here. If a partner genuinely needs to embed you, that is the case where you have to find a way to set a CSP header, there is no header-limited shortcut for it.

**You can only emit a meta tag.** You have no clickjacking protection from CSP, because `frame-ancestors` is ignored in a meta tag and `X-Frame-Options` cannot be set from markup at all (it is a response header with no meta-tag equivalent). The honest answer is that markup alone cannot defend against framing. Treat this as a reason to get even one real security header configured, through the platform, a reverse proxy, an edge worker, or a different host, rather than something a `<meta>` tag can paper over.

`X-Frame-Options` is a legacy header, and once you have a CSP with `frame-ancestors` it is redundant. It is one of the headers worth removing at that point, covered in [legacy security headers to retire](/en/blog/legacy-security-headers-to-retire). Keep it only as the fallback while you cannot set CSP.

## Verify what you actually shipped [#verify-what-you-actually-shipped]

Because the meta-tag failure is silent, check the response your server really sends rather than trusting the markup. The free [security headers scanner](/tools/security-headers) reads the deployed response headers and shows whether `frame-ancestors` (in CSP) or `X-Frame-Options` is present and what value it carries, so you can confirm the page is not framable when you intended it not to be.

If clickjacking protection is part of a wider need to watch what your pages send and load over time, the [CentralCSP CSP suite](/platform/csp-builder) monitors your CSP and the scripts on each page from real traffic, not just a one-off scan. You can [start a free trial](/register) to see the reports and the header state across your site.

## Sources [#sources]

* [MDN, X-Frame-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options)
* [MDN, CSP frame-ancestors](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors)
* [W3C, Content Security Policy Level 3](https://www.w3.org/TR/CSP3/)
* [OWASP, Clickjacking Defense Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Clickjacking_Defense_Cheat_Sheet.html)

## Related [#related]

* [CSP meta tag vs HTTP header](/en/blog/csp-meta-tags-vs-headers)
* [Legacy security headers to retire](/en/blog/legacy-security-headers-to-retire)
* [How to build a strong CSP, step by step](/en/blog/how-to-build-a-strong-csp)
