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.
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 HeadersInterpretation
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.