Skip to content

Clickjacking protection

Severity: lowBehaviour changed in Django 3.0Applies to: Django 3.0+Facts last verified 2026-07-26 against Django 6.0 (main)

X_FRAME_OPTIONS defaults to "DENY" in global_settings.py. It defaulted to "SAMEORIGIN" before Django 3.0, which is why a large amount of otherwise-current advice tells you to set a value that is now weaker than the default.

Rated low deliberately. The header is worth having and costs nothing, but on a site with no framing-sensitive UI the exposure it closes is small — and frame-ancestors in a Content Security Policy supersedes it on every browser that matters.

the fix
settings.py
MIDDLEWARE = [
"django.middleware.clickjacking.XFrameOptionsMiddleware",
# ...
]
# X_FRAME_OPTIONS = "DENY" is already the default — do not set SAMEORIGIN
# unless something genuinely frames your own pages.
verify it workedRun this in: http response
Terminal window
curl -sI https://example.com/ | grep -i x-frame-options
if you see this
You do not have 'django.middleware.clickjacking.XFrameOptionsMiddleware' in your MIDDLEWARE, so your pages will not be served with an 'x-frame-options' HTTP header. Unless there is a good reason for your site to be served in a frame, you should consider enabling this header to help prevent clickjacking attacks.

The middleware is absent, so X_FRAME_OPTIONS is being read and ignored. This is the actual finding on most projects.

Read in: django/core/checks/security/base.py — id security.W002
You have 'django.middleware.clickjacking.XFrameOptionsMiddleware' in your MIDDLEWARE, but X_FRAME_OPTIONS is not set to 'DENY'.

Someone set SAMEORIGIN explicitly. Worth checking whether the reason still exists.

Read in: django/core/checks/security/base.py — id security.W019

The finding is the middleware, not the setting

Section titled “The finding is the middleware, not the setting”

Both warnings are about the same failure in different directions. W002 means the setting is inert because the middleware is missing — the common case, since the setting looks configured in settings.py and produces nothing on the wire. W019 means someone deliberately chose SAMEORIGIN.

If you need framing for one view rather than the whole site, use the decorators instead of weakening the global value:

from django.views.decorators.clickjacking import xframe_options_sameorigin
@xframe_options_sameorigin
def embeddable_widget(request): ...
before you ship this

DENY breaks anything that frames your pages, including your own — embedded preview panes, a legacy admin that iframes a report, an OAuth consent screen shown in a frame. Those break with a blank frame and a console message rather than a server error, so they are easy to miss until someone reports it.

Prefer per-view decorators over lowering the site-wide value.