The problem it solves
Any website can load your website inside an invisible frame. Then it puts its own page on top, with the buttons lined up so that the click the visitor thinks they are making lands on your page instead.
That is clickjacking. The visitor is genuinely logged into your site and genuinely clicked. The click was just aimed by someone else.
X-Frame-Options is your site saying: do not let anyone frame me.
It has exactly two valid values
That is not a simplification. There are two, and no more.
- →DENY means nobody may frame your pages, including you. This is the right choice for most sites.
- →SAMEORIGIN means only your own site may frame your pages. Choose this if you genuinely frame your own content somewhere.
ALLOW-FROM is worse than useless
There used to be a third value, ALLOW-FROM, meant to name a single site allowed to frame you. It is obsolete.
The failure mode is the trap. Modern browsers do not fall back to something sensible when they see it. They ignore the header completely. So a site sending ALLOW-FROM has no frame protection at all, while a scanner reports a frame header present and everyone feels fine about it.
If you need to allow specific sites to frame you, that is frame-ancestors in a Content Security Policy. It is the only way to do it that works.
The meta tag does nothing. At all.
This one is worth stating plainly, because guides recommending it are everywhere.
X-Frame-Options as a meta tag has no effect whatsoever. Not partial, not degraded. Browsers ignore it entirely. It is a real header or it is nothing.
This matters most on hosted platforms where you can inject markup into the head but cannot touch headers. The meta tag will make a page look protected to anyone reading the source, and it protects nothing. If your platform will not send headers, you do not have this protection, and the honest answer is to know that rather than paper over it. Do not confuse this with CSP, which does work partially as a meta tag, though frame-ancestors is one of the parts that does not.
What the modern version looks like
CSP frame-ancestors is the authoritative version now. It supersedes X-Frame-Options, it takes a list of origins instead of one, and where a browser understands both, frame-ancestors wins.
So why send the old header? Legacy coverage. It costs nothing, and it catches anything that has not caught up. Send both.
X-Frame-Options: DENY
Content-Security-Policy: frame-ancestors 'none'frame-ancestors 'none' matches DENY, and frame-ancestors 'self' matches SAMEORIGIN. Keep the two in step, otherwise different browsers will do different things. Both lines must be real response headers. Neither works as a meta tag.
We have step by step instructions for every major platform, including the ones that will not let you.
See how to set frame headers on your platform