← Debugging Guides

阅读中文版本

Browser Refused to Display Frame — X-Frame-Options DENY

Fix "Refused to display in a frame" by setting compatible X-Frame-Options or Content-Security-Policy frame-ancestors for the embedding origin.

intermediatedebuggingbrowsersecurityframes

Symptoms

  • Console: "Refused to display 'https://x' in a frame because it set X-Frame-Options to 'DENY'"
  • An embedded dashboard or auth page shows an empty frame
  • Works same-origin but fails cross-origin

Possible causes

  • The framed page sends X-Frame-Options: DENY
  • CSP frame-ancestors does not include the embedding origin
  • Both headers are set and conflict

Diagnosis steps

Inspect the framing headers

curl -sI https://embedded.example.com | grep -i "x-frame-options\|content-security-policy"

Interpretation

The header value reveals whether framing is denied outright or restricted to specific ancestors.

Reproduce the embedding

DevTools → Network → select the framed document → Response Headers

Interpretation

Confirming which header blocks the frame distinguishes a CSP rule from X-Frame-Options.

Root cause

Browsers refuse to render a document inside a frame when X-Frame-Options is DENY/SAMEORIGIN and the embedder differs, or when CSP frame-ancestors omits the embedder. These headers are the site's clickjacking defense.

Fix

Replace X-Frame-Options with CSP frame-ancestors listing the specific allowed embedders, or set X-Frame-Options: ALLOW-FROM is obsolete—use frame-ancestors. Never use DENY when you intend to be framed.

Risk note

Allowing framing reopens clickjacking risk; restrict frame-ancestors to specific trusted origins.

Verify the fix

Embed from the intended origin and confirm the frame renders; verify an unlisted origin is still blocked.

Symptoms

When you hit “Browser Refused to Display Frame — X-Frame-Options DENY”, guessing wastes time. The guide below follows evidence: reproduce the symptom, run targeted diagnostics, identify the root cause, apply the fix, and verify it with concrete checks.

Observed symptoms include:

  • Console: “Refused to display ‘https://x’ in a frame because it set X-Frame-Options to ‘DENY’”
  • An embedded dashboard or auth page shows an empty frame
  • Works same-origin but fails cross-origin

Reproduce and Diagnose

Reproduce the failure in isolation before changing any code. Work through the checks below in order; each one narrows the search space.

Step 1: Inspect the framing headers

curl -sI https://embedded.example.com | grep -i "x-frame-options\|content-security-policy"

The header value reveals whether framing is denied outright or restricted to specific ancestors.

Step 2: Reproduce the embedding

DevTools → Network → select the framed document → Response Headers

Confirming which header blocks the frame distinguishes a CSP rule from X-Frame-Options.

Likely contributors, in order of frequency:

  • The framed page sends X-Frame-Options: DENY
  • CSP frame-ancestors does not include the embedding origin
  • Both headers are set and conflict

Root Cause

Browsers refuse to render a document inside a frame when X-Frame-Options is DENY/SAMEORIGIN and the embedder differs, or when CSP frame-ancestors omits the embedder. These headers are the site’s clickjacking defense.

The Fix

Replace X-Frame-Options with CSP frame-ancestors listing the specific allowed embedders, or set X-Frame-Options: ALLOW-FROM is obsolete—use frame-ancestors. Never use DENY when you intend to be framed.

Watch out: Allowing framing reopens clickjacking risk; restrict frame-ancestors to specific trusted origins.

# allow embedding only by a specific origin
add_header Content-Security-Policy "frame-ancestors 'self' https://app.example.com" always;

# do NOT set a conflicting X-Frame-Options: DENY alongside it
# (frame-ancestors supersedes X-Frame-Options in modern browsers)

Verify and Prevent

Embed from the intended origin and confirm the frame renders; verify an unlisted origin is still blocked.

Once verified, add a regression test that reproduces the original symptom so the failure cannot silently return.

This class of failure is usually environmental rather than pure logic, so capture the exact command, the environment variables, and the working directory in your bug report. Reproduce it in the smallest possible script, then widen the scope only once the minimal case is understood. A short written checklist for this failure mode saves hours the next time it appears.