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

Add the X-Frame-Options header in one line and stop other sites framing your pages. Step-by-step fixes for Cloudflare, Nginx and Apache, plus how to verify.

http-headersx-frame-optionswebsite-healthguideclickjacking

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 is 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 its own content on top. The visitor thinks they are interacting with a site they trust. They are actually clicking on something they cannot see. This framing and embedding misconfiguration 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 trick. Here are the basic mechanics:

Another site loads your page inside a full-screen iframe with opacity: 0 - invisible, but still interactive. That site then positions its own visible content underneath, aligned precisely so that a visitor who thinks they are clicking its button is really clicking your hidden page.

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.

None of this needs a server-side flaw or a bug in your code. It relies on the browser’s willingness 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 grade. 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 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 with a rule at the edge or in a proxy in front of your site 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 headers 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 CSP 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.

Adding the CSP side is a larger job than this one header. Our guide to fixing a missing Content-Security-Policy header walks through the report-only rollout that gets you frame-ancestors without breaking your pages.


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. Any site 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 only helps on the pages visitors actually land on. 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 behaviour: framing. It says nothing about inline scripts, mixed content or any of the other misconfigurations a Content-Security-Policy header covers. 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:

Guardr reads X-Frame-Options and the CSP frame-ancestors directive together and reports one finding when neither covers the page.

Fixed it on one site? Check whether the other sites you manage are missing X-Frame-Options too - the check on the Guardr homepage takes a few seconds and needs no account.


X-Frame-Options is the quickest header on this list 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.

Keep reading:


See the full scoring breakdown in the Guardr methodology.

Check your site health grade

Free check - no sign-up required.

Scan your site →