How do I fix a missing X-Frame-Options header?

A missing X-Frame-Options header leaves the browser free to render your page inside a frame, iframe, object, or embed on another site, which is the precondition for clickjacking. The X-Frame-Options response header indicates whether a browser should be allowed to render the document in those contexts. The fix is to send that header, or the modern frame-ancestors directive inside a Content-Security-Policy, and to know which one your browser support requires.

Why this matters

Clickjacking overlays transparent or disguised frames so a user clicks controls on your authenticated page while believing they are interacting with the attacker's page. MDN states that if X-Frame-Options is not sent and no other embedding restriction such as frame-ancestors exists, the browser will allow other sites to embed the document. The header accepts DENY, which blocks all framing regardless of origin, or SAMEORIGIN, which permits embedding only when every ancestor frame shares your origin. The obsolete ALLOW-FROM directive is ignored by modern browsers that receive it, so it is not usable for allowlisting. Because the frame-ancestors directive in CSP supersedes X-Frame-Options and supports a list of permitted origins, send both during a migration and treat X-Frame-Options as the fallback for older clients. Choose DENY for pages that are never embedded, such as login, checkout, and administrative screens, where any framing is suspect; choose SAMEORIGIN only for pages you genuinely embed within your own origin. Note that these controls stop framing, not script injection: once an attacker can run JavaScript in your origin, frame restrictions no longer protect the user, so X-Frame-Options belongs alongside a Content-Security-Policy rather than instead of one.

How to fix it

Set the header at the edge or the server on HTML documents. DENY is the strictest option and a safe default for applications that are never meant to be embedded; choose SAMEORIGIN only if you legitimately frame your own pages. The Nginx add_header directive and Apache's Header always set both write the value, Express res.set sends it directly, and the Helmet middleware enables SAMEORIGIN by default. Next.js can attach it through next.config.js, Cloudflare Pages through a _headers file, and a Cloudflare Worker through the headers option of a Response. Where a Content-Security-Policy is also deployed, mirror the decision in frame-ancestors so the two controls agree rather than conflict.

# Nginx (http, server, or location context)
add_header X-Frame-Options SAMEORIGIN always;

# Apache
Header always set X-Frame-Options "SAMEORIGIN"

// Express (Helmet sets SAMEORIGIN by default)
app.use(helmet());

// Express without Helmet
app.use(function (req, res, next) {
  res.set('X-Frame-Options', 'DENY');
  next();
});

// next.config.js
module.exports = {
  async headers() {
    return [
      {
        source: '/:path*',
        headers: [ { key: 'X-Frame-Options', value: 'SAMEORIGIN' } ],
      },
    ]
  },
}

# Cloudflare Pages public/_headers
/*
  X-Frame-Options: DENY

// Cloudflare Workers
export default {
  async fetch() {
    return new Response('ok', {
      headers: { 'X-Frame-Options': 'DENY' },
    });
  },
};

How to verify the fix

Fetch the response headers and confirm X-Frame-Options is present, then confirm frame-ancestors as well if you ship a CSP. The curl command below filters the header list, and DevTools shows both values on the document request. A practical test is to load the page inside an iframe from an unrelated origin; under DENY the frame should be blocked.

curl -sI https://example.com/ | grep -i x-frame-options

Automated check

The Auditor inspects your response headers and reports the absent header as a Missing X-Frame-Options header finding at Medium severity. It checks header presence only; it does not attempt to frame your page or simulate a clickjacking attack end to end. If your app relies on frame-ancestors instead, keep X-Frame-Options present as a fallback so the finding clears and older browsers stay protected.

Scan your app free →

Related issues