Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Add an anti-framing response header in the Nginx configuration that serves your HTML. Use DENY when no page may be framed, or SAMEORIGIN when pages on the same origin must embed it:

server {
    add_header X-Frame-Options "DENY" always;
}

Reload Nginx, then inspect the actual responses from every relevant route. For a modern allowlist of specific external embedding sites, use Content Security Policy (CSP) frame-ancestors rather than the obsolete ALLOW-FROM value.

What X-Frame-Options protects

Clickjacking places a legitimate page inside a frame controlled by an attacker, then disguises or positions controls so a visitor clicks the real site while believing they are interacting with something else. The browser may submit the victim’s authenticated actions to the legitimate site.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

OWASP describes X-Frame-Options as an HTTP response header that indicates whether a browser may render a page in a <frame> or <iframe>. It must be sent as a response header; adding a <meta> element to the document does not provide the same protection. The header is documented by OWASP’s Clickjacking Defense Cheat Sheet.

Choose the right framing policy

Requirement Header value Result
No page may be embedded, including by your own origin X-Frame-Options: DENY Blocks framing everywhere. OWASP recommends this unless you have a known framing requirement.
Pages on the same origin must embed the response X-Frame-Options: SAMEORIGIN Allows ancestors from the same origin; external origins are not authorized.
One or more specific external origins must embed the response Content-Security-Policy: frame-ancestors ... CSP expresses multiple approved origins and is the current control for an allowlist.

Do not use ALLOW-FROM. OWASP and MDN identify it as obsolete; modern browsers do not reliably implement it, and unsupported browsers can fail open. Sending multiple X-Frame-Options fields does not create an external allowlist.

Add the header in Nginx

Strict protection with DENY

Put the directive in the server block that handles the affected virtual host:

server {
    listen 443 ssl;
    server_name example.com;

    add_header X-Frame-Options "DENY" always;

    root /var/www/example;
    index index.html;
}

The always parameter is important when the policy should accompany error responses as well as successful responses and redirects. Nginx documents add_header name value [always]; in its headers module documentation. Without always, Nginx adds the field only for status codes 200, 201, 204, 206, 301, 302, 303, 304, 307 and 308. The always form has been available since Nginx 1.7.5.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Allow same-origin frames

If an application genuinely embeds its own pages, change only the value:

server {
    add_header X-Frame-Options "SAMEORIGIN" always;
}

“Same origin” means the same scheme, host and port. A different subdomain is not automatically the same origin.

Apply it at the correct configuration level

add_header is valid in http, server and location contexts. A server-level directive is convenient, but it does not prove that every response in a complex deployment carries the field. Nested locations, proxied applications, a CDN and another reverse proxy can change the effective response.

Nginx’s normal inheritance rule is easy to miss: a child context inherits parent add_header directives only when that child contains no add_header directives of its own. For example, this location stops inheriting the server-level X-Frame-Options line:

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
server {
    add_header X-Frame-Options "DENY" always;

    location /app/ {
        add_header Content-Security-Policy "default-src 'self'" always;
    }
}

On Nginx 1.29.3 and later, add_header_inherit merge; can append parent headers to directives declared in a child context:

server {
    add_header_inherit merge;
    add_header X-Frame-Options "DENY" always;

    location /app/ {
        add_header Content-Security-Policy "default-src 'self'" always;
    }
}

This option was introduced in Nginx 1.29.3, so confirm the deployed version before using it. The example’s CSP is only an inheritance illustration; a real policy must account for the site’s scripts, styles, images, frames and other resources. See the Nginx 1.29.3/1.29.4 release article for the version context.

Use CSP for selected external embedding sites

X-Frame-Options cannot express a reliable list of several external origins. CSP’s frame-ancestors directive can:

server {
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header Content-Security-Policy "frame-ancestors 'self' https://partner.example" always;
}

Replace the example origin with the exact scheme and host you authorize. Use frame-ancestors 'none'; to prohibit all ancestors or frame-ancestors 'self'; for same-origin ancestors. The special source values 'self' and 'none' require the quotes shown.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Deliver frame-ancestors as an HTTP response header, not a meta element. When a browser supports both policies, it gives precedence to CSP’s frame-ancestors; retaining X-Frame-Options can still provide compatibility for older browsers. MDN explains this behavior in its clickjacking guide.

Deploy the change safely

  1. Identify the responses. List public HTML routes, authenticated pages, error pages and any endpoint intentionally embedded by another application. Decide whether each needs DENY, SAMEORIGIN or a CSP allowlist.
  2. Find the effective configuration. Locate the active virtual host and any nested location blocks. Check whether an upstream, CDN or other proxy already sets, removes or rewrites the header.
  3. Edit the configuration. Add the directive in the context serving those routes, normally with always. Do not put it only in an unrelated server block.
  4. Validate syntax. Run the locally installed Nginx test command with the privileges used by your service account:
    sudo nginx -t

    Continue only when it reports that the syntax test is successful.

  5. Reload using your normal service procedure. A reload lets workers pick up the validated configuration without treating it as a new policy for unrelated hosts. Use your operating system’s documented Nginx service command.
  6. Check representative responses. Test the homepage, nested locations, redirects, an authenticated route where appropriate, and an error response if errors should carry the policy. A single homepage check cannot establish coverage for the whole deployment.

Verify the response header

cURL

Use a real URL and inspect headers without downloading the body:

curl -sSI https://example.com/

For a route that redirects, follow the behavior deliberately rather than assuming the first response is the final document. To inspect a specific status-producing path, request that path directly:

curl -sS -D - -o /dev/null https://example.com/app/

Look for exactly one effective X-Frame-Options value and, when used, the intended Content-Security-Policy field with frame-ancestors.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Python check

This small script prints the status and relevant fields for a route. It does not replace testing all locations:

import requests

url = "https://example.com/"
r = requests.get(url, allow_redirects=False, timeout=20)
print(r.status_code)
print("X-Frame-Options:", r.headers.get("X-Frame-Options"))
print("Content-Security-Policy:", r.headers.get("Content-Security-Policy"))

Node.js check

On a current Node.js release with built-in fetch:

const res = await fetch('https://example.com/', { redirect: 'manual' });
console.log(res.status);
console.log('X-Frame-Options:', res.headers.get('x-frame-options'));
console.log('Content-Security-Policy:', res.headers.get('content-security-policy'));

Run these checks from a network position that reaches the production proxy or CDN. Internal tests can miss an edge layer that modifies headers.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Troubleshoot missing or ineffective protection

Symptom Likely cause Fix
Header appears on 200 responses but not 404 or 500 responses always is absent. Add always, reload after nginx -t, and retest the error route.
Header is present on the homepage but absent under a path A nested location has its own add_header and therefore stopped inheriting the parent. Repeat the security header in that location, restructure the directives, or use add_header_inherit merge on Nginx 1.29.3 or newer.
Two different X-Frame-Options values are returned Nginx, the application, a proxy or a CDN is adding another field. Inspect each layer’s effective configuration and leave one deliberate policy. Do not combine values to form an allowlist.
A partner iframe is blocked DENY blocks every ancestor, or SAMEORIGIN excludes an external origin. Confirm the business requirement, then use CSP frame-ancestors with the partner’s exact origin.
An obsolete ALLOW-FROM value seems to work in one browser Browser support is inconsistent and unsupported browsers may fail open. Replace it with CSP frame-ancestors; retain X-Frame-Options only as a compatibility fallback.
Configuration test passes but production still lacks the field The edited file is not the active virtual host, or an upstream/CDN removes the header. Inspect the loaded configuration, confirm DNS and proxy routing, and compare headers at each layer.

Operational and security considerations

  • Apply the policy to sensitive HTML, especially account, administration and transaction pages. An attacker may target a less obvious route rather than the homepage.
  • Document intentional framing exceptions. A later developer should not “fix” an apparently missing header by weakening the policy globally.
  • Test login flows and embedded widgets after enabling DENY or SAMEORIGIN; legitimate integrations can break by design.
  • Do not treat the header as a substitute for CSRF defenses, authorization checks, secure cookies or other browser security controls. It addresses frame-based deception.
  • Recheck headers after Nginx upgrades, virtual-host changes, CDN migrations and new location blocks, because inheritance and response handling are configuration-dependent.

Or skip the browser setup

If you need a clean visual record of the deployed page while checking a security change, ScreenshotNeo returns a screenshot or PDF from one GET request. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups and chat widgets; each cleanup step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and response headers identify the page verdict and billing result. Its MCP server provides take_screenshot, get_page_info and capture_pdf tools for Claude, Cursor and other MCP clients.

Example request (see the ScreenshotNeo API documentation):

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp

The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots, and every feature is available on every plan. Create a free ScreenshotNeo account.

Frequently Asked Questions

Does X-Frame-Options protect an API response that is never rendered as a document?

It is primarily a browser rendering control for framed documents. For APIs, enforce authentication, authorization, CSRF protections where applicable, and correct content types; add the header consistently if those responses might ever be rendered in a browser.

Can I test clickjacking protection without deploying a public exploit page?

Yes. Verify the response headers with cURL, Python or Node, then use a controlled internal frame test to confirm the intended browser behavior. Do not test against users or third-party sites without authorization.

Should I add X-Frame-Options in Nginx and in the application?

Choose one authoritative layer when possible. If both layers emit the field, inspect the final response for duplicate or conflicting values and remove ambiguity.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.