Stopping your pages being framed by somebody else
Clickjacking puts your page in a transparent frame over somebody else's buttons, so a visitor who thinks they are clicking one thing clicks yours. One header prevents it, a newer directive replaced that header, and there is still a reason to send both.
The short answer
X-Frame-Options stops other sites loading your pages in a frame, which is what clickjacking requires. It is superseded by the CSP frame-ancestors directive, and it is still worth sending: the directive is what modern browsers enforce, and the header covers the clients that predate CSP level 2 for the cost of one line.
The attack, and why framing enables it
An attacker loads your page in an iframe, sets its opacity to zero, and positions it over their own page so that your buttons sit exactly beneath theirs. The visitor sees a game, a video player or a competition entry, clicks where they were invited to click, and the click lands in your page instead.
Because the click carries the visitor's own session, it does whatever they were authorised to do: change a setting, accept a permission, confirm a transfer, add a permission to an account. No credentials are stolen and no request is forged, which is what makes this different from the attacks the usual defences cover. The visitor really did click, on your real page, while logged in.
The defence is to refuse to be framed. Since the frame has to exist for the attack to work, declining to render inside one removes the whole class of problem.
# the old header. two values worth using
X-Frame-Options: DENY # never framed, by anyone
X-Frame-Options: SAMEORIGIN # framed only by your own pages
# the modern equivalent, inside a content security policy
Content-Security-Policy: frame-ancestors 'none'
Content-Security-Policy: frame-ancestors 'self'
Content-Security-Policy: frame-ancestors 'self' https://partner.example.com
# nginx, sending both
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Content-Security-Policy "frame-ancestors 'self'" always;The third policy line is the reason the directive replaced the header. X-Frame-Options had an ALLOW-FROM value for naming a permitted origin, it was never implemented consistently, and it accepted only one origin. frame-ancestors takes a list and works, which is the whole upgrade.
Why send a superseded header
frame-ancestors supersedes the header, and where both are present the directive wins in browsers that support it. So the older header is redundant in any current browser, and the recommendation is still to send both. Three reasons, in decreasing order of how much they matter.
- You may not have a policy yet. The most practical reason.
frame-ancestorslives inside a content security policy, and introducing one properly is a fortnight of report-only. The header is one line and protects you today. - A policy can be replaced. If somebody later sets a policy without a
frame-ancestorsdirective, the protection disappears with no error. A separate header does not depend on a directive surviving a configuration change. - Old clients still exist. A shrinking argument, and it costs nothing to keep.
The one thing to avoid is sending values that contradict each other, a DENY header next to a frame-ancestors 'self' directive. Both will be obeyed by whichever client understands each, so different visitors get different behaviour, and the version somebody debugs is not the version somebody else is seeing.
Decide whether anything legitimately frames you before choosing a value.
DENY is the safe default and it breaks any embed of your own pages: a preview inside your admin interface, a widget you offer to partners, a documentation page embedded in a product tour, an editor previewing a draft. Those are exactly the things nobody remembers until they stop working.
SAMEORIGIN keeps your own embeds working and is the right choice for most sites. If a partner genuinely needs to frame you, that is the case for a policy with a named origin rather than for weakening the header.
Delivered in a header, on every response
Framing protection is a response header. There is nothing in the markup to read, which is why this check needs something that looks at how a page was delivered rather than at what it contains.
This one needs the full crawl
Framing protection arrives as a response header, and the payload behind this search is built from what the document says rather than from how it was delivered.
The instant search on this site audits a single page, so rather than show you a verdict it cannot support, this guide sends you to the place the check actually runs.
The immediate version is one request: curl -sI https://example.com | grep -i frame. What that misses is whether the header is applied consistently across every route, which is where a rule set at the application level and bypassed by a static route goes wrong.
Does this affect AI search?
No. This header changes what a browser does when a page is placed inside a frame, and nothing about retrieval, indexing or citation involves framing. A page with DENY and a page with no header at all are identical to anything fetching them.
It is worth naming one thing this is sometimes confused with. Refusing to be framed does not restrict what may quote or summarise your content: the header governs rendering inside another document, not reuse of your text. If restricting reuse is what you want, the tools are the snippet directives and the AI crawler entries in your robots.txt, and they are separate decisions with separate costs.
The honest summary: this is a security header in a catalogue that reports what is true about a page, and there is no visibility argument for or against it.
Why a one-line header is rated Important
Because the attack it prevents is real, requires no vulnerability in your code, and cannot be defended against any other way. There is no amount of careful engineering inside your application that stops your page being framed; the only defence is telling the browser not to allow it.
It stays out of the Critical band because nothing about your site's visibility depends on it, and because the attack needs a visitor to be lured to an attacker's page while logged into yours. That is a real scenario and not a passive one.
What holds the rating up rather than letting it drop to a refinement is the cost of the fix. Everything else rated Important in this audit involves work: content, templates, decisions. This is one line in a configuration file, with a known safe value, applied everywhere at once. A finding that is both genuinely protective and free to close is not a refinement; it is something that has been overlooked.
Questions this check raises
- Is X-Frame-Options obsolete?
- It is superseded, not useless. Browsers that support CSP level 2 enforce frame-ancestors and ignore the header when both are present, so the modern policy is the one doing the work. The header costs one line and covers the rest, which is why the usual recommendation is to send both.
- What is clickjacking?
- Loading your page invisibly over an attacker page so a click intended for their interface lands on your control instead. It needs your page to be frameable, which is exactly what this header refuses. It matters most on pages with a state-changing action behind a single click.
- Should I use DENY or SAMEORIGIN?
- DENY unless you frame your own pages somewhere. SAMEORIGIN permits framing from your own origin, which some applications need for previews or embeds. The old ALLOW-FROM value is not supported by modern browsers and should not be used; frame-ancestors is how you allow a specific origin.