# CSP meta tag vs HTTP header (/en/blog/csp-meta-tags-vs-headers)



You can deliver a Content Security Policy (CSP) two ways: as an HTTP response
header, or as a `<meta>` tag in your HTML. They look interchangeable, and for a
simple `default-src 'self'` they mostly are. But the header is the real one. It
applies earlier and supports every part of the policy, while a meta tag starts
late and silently drops several directives. This post shows both, explains the
gaps, and tells you when a meta tag is an acceptable fallback.

## The two ways to deliver a policy [#the-two-ways-to-deliver-a-policy]

As a header, sent with the response before any HTML is parsed:

```http
Content-Security-Policy: default-src 'self'; script-src 'self'
```

As a meta tag, which must sit inside `<head>`:

```html
<head>
  <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
</head>
```

Both are standard and widely supported. The difference is not whether they work,
it is what each one can do and when it kicks in.

|                            | HTTP header                             | `<meta>` tag                               |
| -------------------------- | --------------------------------------- | ------------------------------------------ |
| Applies from               | Before the first byte of HTML is parsed | Only to content **after** the meta element |
| Covers preloaded resources | ✅ Yes                                   | ❌ No                                       |
| `frame-ancestors`          | ✅ Enforced                              | ❌ Ignored                                  |
| `report-uri`               | ✅ Enforced                              | ❌ Ignored                                  |
| `sandbox`                  | ✅ Enforced                              | ❌ Ignored                                  |
| Report-Only mode           | ✅ `Content-Security-Policy-Report-Only` | ❌ No equivalent                            |
| Violation reporting        | ✅ Via `Reporting-Endpoints`             | ❌ Cannot set a response header             |
| Changeable after parsing   | ❌ No (nor is it meant to be)            | ❌ No, edits to `content` are ignored       |
| Needs server or CDN config | Yes                                     | No, it is just markup                      |

Everything below explains the rows.

## Why the header is the default [#why-the-header-is-the-default]

### A meta policy starts late [#a-meta-policy-starts-late]

A header is delivered before the browser parses a single byte of your HTML, so it
governs the whole document, including resources the browser preloads. A meta tag is
just markup: the policy only applies to content the parser reaches **after** the
meta element. Anything before it is not covered.

That is a real security gap, not a technicality. If a script tag sits above your
meta tag, or an attacker injects markup earlier in the document, it runs before the
policy exists. [CSP Level 3 §3.3](https://www.w3.org/TR/CSP3/#meta-element) is explicit
that policies delivered in meta elements are not applied to content that precedes them,
which is why the meta tag has to go as early in `<head>` as possible. You also cannot change a meta policy after the fact: editing
the `content` attribute with JavaScript after parsing is ignored.

### Some directives only work in a header [#some-directives-only-work-in-a-header]

Three directives are [defined to be ignored](https://www.w3.org/TR/CSP3/#meta-element)
when the policy comes from a meta tag:

* [`frame-ancestors`](/en/docs/web-security/policies/content-security-policy/directives/frame-ancestors),
  which controls who can embed your page (your clickjacking defense).
* [`report-uri`](/en/docs/web-security/policies/content-security-policy/directives/report-uri),
  the legacy reporting directive.
* [`sandbox`](/en/docs/web-security/policies/content-security-policy/directives/sandbox),
  which applies sandbox restrictions to the document.

Put any of those in a meta tag and the browser drops them while keeping the rest of
the policy. So a meta-delivered policy cannot protect against framing at all.

### No report-only, no reporting [#no-report-only-no-reporting]

You also cannot run a meta policy in test mode. The
[`Content-Security-Policy-Report-Only`](/en/docs/web-security/policies/content-security-policy/report-only)
header has no meta-tag equivalent, so the safe way to roll out a policy, watch
violations without blocking anything, is header-only.

Reporting does not really work from a meta tag either. The modern
[`report-to`](/en/docs/web-security/policies/content-security-policy/directives/report-to)
directive names an endpoint that you define with the
[`Reporting-Endpoints`](/en/docs/web-security/reporting-api/headers/reporting-endpoints)
response header (see [Report-To vs Reporting-Endpoints](/en/blog/report-to-vs-reporting-endpoints) for how the endpoint headers differ),
and a meta tag cannot set a response header, so there is nowhere
for reports to go. If you want violation reports, you want headers. See
[get started with CSP reporting](/en/blog/get-started-csp-reporting) for the full
setup.

## When a meta tag is acceptable [#when-a-meta-tag-is-acceptable]

Sometimes you genuinely cannot set response headers: a static host or CDN that does
not let you configure them, a CMS where you only control the page template, a quick
prototype. In those cases a meta tag is better than no policy at all. Keep two
things in mind:

* Put the meta tag **first** in `<head>`, before any other tag that loads a
  resource, so the gap before it is as small as possible.
* Accept that you lose `frame-ancestors`, report-only testing, and reporting. For
  [clickjacking protection when you cannot set a CSP header](/en/blog/frame-ancestors-without-csp-header), the older
  [`X-Frame-Options`](/en/docs/web-security/security-headers/x-frame-options)
  header is sometimes available even when the host limits which headers you can set. It is one of several [legacy security headers worth retiring](/en/blog/legacy-security-headers-to-retire) once a CSP covers the same ground.

## Using both: they stack, they do not merge [#using-both-they-stack-they-do-not-merge]

If you send a header policy and a meta policy at the same time, the browser does not
combine them into one. It enforces **both, independently**, and a resource has to
satisfy every policy to load. The practical result is the intersection: the most
restrictive rule wins, and a second policy can only add restrictions, never loosen
them.

```http
Content-Security-Policy: default-src 'self'
```

```html
<meta http-equiv="Content-Security-Policy" content="default-src 'self' https://cdn.example">
```

Here the meta tag allows `https://cdn.example`, but the header does not, so the
resource is still blocked. The meta policy cannot widen what the header allows. Two
enforced policies always combine this way, by intersection.

## The recommendation [#the-recommendation]

Use a header. Deliver your Content Security Policy with the
`Content-Security-Policy` response header, start in report-only to shake out false
positives, and add reporting so you can see what the policy blocks. Reach for a meta
tag only when you cannot set headers, and know what you are giving up. CentralCSP's free
[CSP scanner](/tools/csp-scanner) checks whatever you ship, header or meta, and tells you
which directives a meta-delivered policy is silently dropping; once you are on headers,
CentralCSP collects the violation reports the meta tag could never have sent.

## Next steps [#next-steps]

* New to policies? Start with [get started with Content Security Policy](/en/blog/get-started-with-csp).
* Ready for a full policy? See [how to build a strong CSP](/en/blog/how-to-build-a-strong-csp).
* Want violation reports? [Get started with CSP reporting](/en/blog/get-started-csp-reporting), then [create a free account](/register).

## Sources [#sources]

* [W3C, CSP Level 3 - the meta element](https://www.w3.org/TR/CSP3/#meta-element)
* [W3C, CSP Level 3 - multiple policies](https://www.w3.org/TR/CSP3/#multiple-policies)
* [MDN, Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy)
* [MDN, meta http-equiv](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/meta#http-equiv)
