Guides / X-Frame-Options

Security Header Guide

How to Fix a Missing X-Frame-Options Header (Clickjacking Risk)

A missing X-Frame-Options header lets attackers embed your site invisibly inside an iframe and trick your users into clicking things they didn’t mean to. Here’s what that attack looks like and exactly how to stop it.

What is Clickjacking?

Clickjacking (also called a “UI redress attack”) is when an attacker overlays your site — invisible or disguised — inside an <iframe>on their malicious page. The user thinks they’re interacting with something harmless, but their clicks actually land on your real site underneath.

Common clickjacking scenarios include:

  • Tricking users into clicking “Confirm” on a bank transfer or account deletion buried in an invisible iframe.
  • Manipulating users into granting OAuth permissions or enabling account settings they didn’t intend to change.
  • Harvesting credentials when a password manager auto-fills a login form framed invisibly on a malicious page.

The defense is simple: tell the browser that your page is never allowed to be loaded inside an iframe (or restrict it to specific trusted origins).

What is X-Frame-Options?

X-Frame-Options is an HTTP response header that controls whether a browser is allowed to render a page inside an <iframe>, <frame>, or <embed>. It has three values:

DENY

The page cannot be displayed in any iframe, regardless of origin. Recommended for most sites.

SAMEORIGIN

The page can only be framed by pages on the same origin (same scheme, host, and port). Use this if your own site legitimately embeds pages within iframes.

ALLOW-FROM uri

Deprecated — only allows a specific URI but is not supported in Chrome/Firefox. Use CSP frame-ancestors instead.

The Modern Alternative: CSP frame-ancestors

X-Frame-Options is widely supported but technically superseded by the frame-ancestorsdirective in Content-Security-Policy. They do the same job, but CSP’s version is more flexible and supports multiple trusted origins:

# Equivalent to X-Frame-Options: DENY
Content-Security-Policy: frame-ancestors 'none';

# Equivalent to X-Frame-Options: SAMEORIGIN
Content-Security-Policy: frame-ancestors 'self';

# Allow framing only from a specific trusted domain
Content-Security-Policy: frame-ancestors https://partner.example.com;

Best practice: Set both X-Frame-Options: DENY and Content-Security-Policy: frame-ancestors 'none' for maximum browser compatibility.

How to Fix X-Frame-Options on Nginx

Add both headers in your HTTPS server block:

server {
    listen 443 ssl;
    server_name example.com;

    # Prevent clickjacking
    add_header X-Frame-Options "DENY" always;

    # Modern equivalent (add to your existing CSP or as standalone)
    add_header Content-Security-Policy "frame-ancestors 'none';" always;
}

If you have an existing CSP header, append frame-ancestors 'none'; to that policy string rather than adding a second separate CSP header. After editing, test with sudo nginx -t then reload: sudo nginx -s reload.

How to Fix X-Frame-Options on Apache

Ensure mod_headers is enabled (sudo a2enmod headers), then add to your VirtualHost:

<VirtualHost *:443>
    ServerName example.com

    Header always set X-Frame-Options "DENY"
    Header always set Content-Security-Policy "frame-ancestors 'none';"
</VirtualHost>

After editing, restart or reload Apache: sudo systemctl reload apache2.

How to Fix X-Frame-Options on Cloudflare

Use Cloudflare Transform Rules to inject the header at the edge:

  1. In the Cloudflare dashboard, go to Rules → Transform Rules → Modify Response Header.
  2. Click Create Rule. Set the match expression to true to apply to all responses.
  3. Under Then, choose Add: Header name x-frame-options, Value DENY.
  4. Add a second header: content-security-policy with value frame-ancestors 'none';.
  5. Click Deploy. Changes take effect at the edge within seconds.

How to Verify the Header is Set

Use curl to check the headers your server is sending:

curl -sI https://example.com | grep -i -E "x-frame|frame-ancestors"

You should see:

x-frame-options: DENY
content-security-policy: frame-ancestors 'none';

You can also verify in Chrome DevTools: Network tab → click your page’s request → Response Headers. Look for x-frame-options. To test that the protection actually works, create a test HTML file with <iframe src="https://example.com"></iframe> and open it locally — the browser should show a blank iframe with a console error like "Refused to display in a frame because it set 'X-Frame-Options' to 'deny'."

Is your site vulnerable to clickjacking?

Run a free Vigilai scan to instantly check X-Frame-Options, CSP, HSTS, TLS, and 4 other security headers — no signup required.