How to Fix Missing X-Frame-Options Header (Step-by-Step)

Your site is missing the X-Frame-Options header. Learn what clickjacking is, why X-Frame-Options stops it and how to add it on Cloudflare, Nginx and Apache.

security-headersx-frame-optionsweb-securityguideclickjacking

If you have scanned your website and received a warning about a missing X-Frame-Options header, the fix is one line of configuration. This guide explains what clickjacking is, how X-Frame-Options stops it and exactly how to add it on Cloudflare, Nginx and Apache.

What’s covered


What is X-Frame-Options and why does it matter?

X-Frame-Options is an HTTP response header that controls whether a browser is allowed to render your page inside an <iframe>, <frame> or <object> element on another domain.

Without it, any website in the world can silently embed your page in an invisible iframe and layer their own content on top. The user thinks they are interacting with a trusted site. They are actually clicking on something they cannot see. This attack is called clickjacking.

The fix is a single header. It takes less than a minute to add and requires no changes to your application code.


How clickjacking works

Clickjacking is a UI redress attack. Here is the basic mechanics:

An attacker creates a page and embeds your site in a full-screen iframe with opacity: 0 - invisible, but still interactive. They then position their own visible content underneath, aligned precisely so that clicking their button triggers a click on your hidden page instead.

The consequences depend on what your page lets authenticated users do. Common clickjacking targets include:

  • One-click actions - “Delete account,” “Transfer funds,” “Confirm payment,” “Grant permissions.” The user sees an innocent button. Behind it, invisibly, they are clicking a destructive action on your site while already logged in.
  • Social media engagement - invisible Like or Share buttons embedded under fake content. The user thinks they are clicking a prize claim. They are sharing a scam post to all their followers.
  • OAuth authorization flows - the “Authorize this app” button embedded invisibly. The user unknowingly grants a malicious app access to their account.
  • Form input capture - text fields on your site overlaid with decoy fields. What the user thinks they are typing into a visible form is actually going into your login field.

The attack requires no server-side exploit and no vulnerability in your code. It abuses the browser’s ability to render any page in an iframe - unless you tell it not to.


How Guardr scores a missing X-Frame-Options header

Guardr checks for X-Frame-Options as part of the security headers category, which accounts for 28% of your total security score. The other headers in this category are CSP, X-Content-Type-Options, Referrer-Policy and Permissions-Policy.

A missing X-Frame-Options is flagged as a medium severity finding when no frame-ancestors directive is present in your CSP either. If your CSP already includes frame-ancestors, Guardr treats clickjacking protection as covered and does not penalise the absence of X-Frame-Options - because CSP’s frame-ancestors is the modern standard and takes precedence in all current browsers.

In the Guardr dashboard, the finding includes platform-specific fix instructions for Cloudflare, Nginx and Apache - the same three platforms covered in detail below.


How to add X-Frame-Options on Cloudflare

Option 1: _headers file (Cloudflare Pages)

If you are deploying via Cloudflare Pages, create or edit a _headers file in your project’s public output directory:

/*
  X-Frame-Options: SAMEORIGIN

The /* applies the header to every page on your site. Deploy and Cloudflare Pages serves it on every response.

Option 2: Cloudflare Transform Rules (Cloudflare proxy)

If you are using Cloudflare as a proxy in front of your origin server, go to Cloudflare Dashboard → your site → Rules → Transform Rules → Modify Response Header. Create a rule that sets X-Frame-Options to SAMEORIGIN and match it against all requests.

One important note for Cloudflare Pages users: Cloudflare automatically injects a Referrer-Policy header as a platform default. Do not add X-Frame-Options via WAF rules if you are already setting it in your _headers file - you will end up with duplicate headers, which some browsers handle inconsistently.


How to add X-Frame-Options on Nginx

Add the header inside your server block for the HTTPS configuration:

server {
    listen 443 ssl http2;
    server_name example.com;

    add_header X-Frame-Options "SAMEORIGIN" always;

    # ... rest of your config
}

The always keyword ensures the header is sent on error responses (4xx and 5xx) as well as successful ones. Without it, Nginx only adds headers on 2xx responses - meaning your error pages remain frameable.

Test and reload after making changes:

nginx -t
systemctl reload nginx

How to add X-Frame-Options on Apache

Enable the headers module if it is not already active, then add the header in your VirtualHost configuration or .htaccess file.

VirtualHost configuration:

# a2enmod headers  (if not already enabled)

<VirtualHost *:443>
    ServerName example.com

    Header always set X-Frame-Options "SAMEORIGIN"

    # ... rest of your config
</VirtualHost>

.htaccess (shared hosting):

<IfModule mod_headers.c>
    Header always set X-Frame-Options "SAMEORIGIN"
</IfModule>

Restart Apache after changes:

systemctl restart apache2

How to add X-Frame-Options on other platforms

Vercel - add to vercel.json:

{
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        {
          "key": "X-Frame-Options",
          "value": "SAMEORIGIN"
        }
      ]
    }
  ]
}

Netlify - add to netlify.toml:

[[headers]]
  for = "/*"
  [headers.values]
    X-Frame-Options = "SAMEORIGIN"

WordPress - if you cannot edit server config, use a security plugin such as “HTTP Headers” or “Really Simple SSL.” Server-level configuration is always preferred over PHP-level, as PHP headers only apply to WordPress-generated responses and not to static assets.


X-Frame-Options vs CSP frame-ancestors

X-Frame-Options was introduced in 2008 and has near-universal browser support. The Content Security Policy frame-ancestors directive is its modern replacement and offers more control - but the two are not identical and are not interchangeable in all situations.

Here is how they compare:

X-Frame-OptionsCSP frame-ancestors
Browser supportAll browsers including IEAll modern browsers
Allow specific domainsNo (SAMEORIGIN or DENY only)Yes (frame-ancestors https://partner.com)
Takes precedenceIgnored if CSP frame-ancestors presentOverrides X-Frame-Options in modern browsers
Header syntaxSimplePart of full CSP string

The practical recommendation:

Set both. X-Frame-Options: SAMEORIGIN covers older browsers that do not understand CSP. frame-ancestors 'self' in your CSP handles modern browsers and gives you finer-grained control if you ever need it. Guardr treats clickjacking as covered if either is present - but having both costs nothing and maximises compatibility.

If you need to allow a specific external domain to embed your page (a partner portal, an embedded widget you control), use frame-ancestors in your CSP - X-Frame-Options cannot do this. The ALLOW-FROM value was deprecated and is no longer supported by any current browser.


Understanding the X-Frame-Options values

The header accepts two valid values in current browsers:

SAMEORIGIN - the browser allows the page to be framed only by pages on the same origin (same protocol, domain and port). This is the recommended value for most sites. Use it when you embed your own pages in iframes - for example, a dashboard that loads sub-views in frames, or a site that uses iframes for internal navigation.

DENY - the browser blocks all framing, including from the same origin. Use this if your site has no legitimate use case for being embedded in an iframe anywhere. Login pages, admin panels and financial dashboards are good candidates for DENY.

A third value, ALLOW-FROM uri, was once part of the specification but is now deprecated and unsupported by Chrome, Firefox and Safari. Do not use it. If you need to allow a specific domain to embed your page, use CSP frame-ancestors instead.


Common mistakes when setting X-Frame-Options

Setting it only on the homepage. Clickjacking targets specific actions - the checkout page, the account settings page, the password change form. The header needs to apply to every page, not just the root. Configure it at the server or CDN level so it is served on all routes.

Using ALLOW-FROM. This value is deprecated and ignored by all major browsers. Attackers can frame your page in Chrome or Firefox regardless of what ALLOW-FROM says. Use CSP frame-ancestors if you need to allowlist a specific domain.

Sending it over HTTP responses only. The header protects pages served to users. Make sure it is set on your HTTPS configuration, not just on HTTP redirects that no one is landing on.

Duplicate headers from multiple sources. If you set X-Frame-Options in both your application code and your CDN, browsers may receive two conflicting values. Some treat the first as authoritative, others the last. Consolidate to a single location - CDN or server config, not both.

Assuming it replaces a CSP. X-Frame-Options addresses one specific attack vector: framing. It does not protect against XSS, mixed content, or any of the other threats a Content Security Policy addresses. Set both.


How to verify X-Frame-Options is working

Using curl:

curl -I https://yoursite.com

Look for x-frame-options in the response headers. You should see SAMEORIGIN or DENY.

Using browser DevTools:

Open your site in Chrome or Firefox, press F12, go to the Network tab, click the main document request and check the Response Headers section.

Testing that framing is actually blocked:

Create a local HTML file with this content, replacing the URL with your domain:

<!DOCTYPE html>
<html>
  <body>
    <iframe src="https://yoursite.com" width="800" height="600"></iframe>
  </body>
</html>

Open it in your browser. If X-Frame-Options is working, the iframe will be blank and the browser console will show a message like:

Refused to display 'https://yoursite.com/' in a frame because it set 'X-Frame-Options' to 'sameorigin'.

Using Guardr:

Scan your site for free at guardr.io - Guardr checks for X-Frame-Options and CSP frame-ancestors together and reports whether clickjacking protection is in place. If the header is missing and no frame-ancestors directive covers it, you will see the finding with the fix instructions for your platform.


X-Frame-Options is the simplest security header to add. One line, no configuration decisions beyond SAMEORIGIN vs DENY, no testing phase required. If your site has any authenticated actions - anything a logged-in user can do with a single click - add this header today.

Scan your site and see your current security score →


See the full scoring breakdown in the Guardr methodology.

Check your website's security score

Free scan - no signup required.

Scan your site →