Instagram embeds are one of those things that look simple until CSP gets involved.

You paste the embed code, reload the page, and suddenly the post is blank, the console is yelling about blocked frames or scripts, and someone suggests adding https: to half your policy. That usually “works,” but it also wrecks the point of having CSP in the first place.

If you want Instagram embeds and a CSP that still means something, you need to decide which tradeoff you’re willing to accept.

The short version

For Instagram embeds, you’ll usually need to allow some combination of:

  • frame-src for Instagram’s embedded frame content
  • script-src for Instagram’s embed script if you use their JavaScript-based embed flow
  • img-src for Instagram-hosted media and tracking pixels
  • connect-src in some setups, depending on what the embed script does
  • style-src only if your own integration injects inline styles or third-party UI does something annoying

The exact hostnames can change over time, which is part of the problem. That means there are really a few strategies, each with different security and maintenance costs.

A good baseline: start strict, then open only what breaks

I like starting from a policy shaped roughly like this real-world example from headertest.com:

Content-Security-Policy:
  default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com;
  script-src 'self' 'nonce-OTYyMTQyOWEtZTc2Yi00ZDMyLWI4MWQtOTM1ZDBiNTU4YjZi' 'strict-dynamic' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com;
  style-src 'self' 'unsafe-inline' https://www.googletagmanager.com https://*.cookiebot.com https://consent.cookiebot.com;
  img-src 'self' data: https:;
  font-src 'self';
  connect-src 'self' https://api.headertest.com https://tallycdn.com https://or.headertest.com wss://or.headertest.com https://*.google-analytics.com https://*.googletagmanager.com https://*.cookiebot.com;
  frame-src 'self' https://consentcdn.cookiebot.com;
  frame-ancestors 'none';
  base-uri 'self';
  form-action 'self';
  object-src 'none';

That’s a sane starting point because it’s explicit. It doesn’t pretend all third-party content is harmless. To support Instagram, you extend the relevant directives instead of blowing a hole through default-src.

Option 1: Allow Instagram only in frame-src

This is the simplest option if your embed is just an iframe and you are not loading Instagram’s JavaScript.

Example:

Content-Security-Policy:
  default-src 'self';
  frame-src 'self' https://www.instagram.com;
  img-src 'self' data: https:;
  script-src 'self';
  style-src 'self';
  object-src 'none';
  base-uri 'self';
  form-action 'self';

Pros

  • Smallest blast radius
  • Easy to reason about
  • Keeps script-src tight
  • Best choice if your app renders a plain iframe and nothing else

Cons

  • Often not enough for Instagram’s official embed flow
  • May break if Instagram serves content from additional subdomains
  • You still need to verify media and supporting requests in DevTools

When I’d use it

If I control the markup and can keep the embed as a plain iframe, this is my first attempt. If it works, great. If it doesn’t, I move to the next option instead of preemptively allowing a bunch of script origins.

Option 2: Allow Instagram embed script in script-src

A lot of teams use Instagram’s official embed markup plus their script. That means CSP has to allow that script source.

Typical shape:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://www.instagram.com;
  frame-src 'self' https://www.instagram.com;
  img-src 'self' data: https:;
  style-src 'self';
  object-src 'none';
  base-uri 'self';
  form-action 'self';

If your page uses nonces, keep doing that. Don’t throw away a good script-src design just because a third-party embed showed up.

Example with a nonce-based setup:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-r4nd0m' https://www.instagram.com;
  frame-src 'self' https://www.instagram.com;
  img-src 'self' data: https:;
  style-src 'self';
  object-src 'none';
  base-uri 'self';
  form-action 'self';

And your script tag:

<script async nonce="r4nd0m" src="https://www.instagram.com/embed.js"></script>

Pros

  • Supports the official JavaScript-driven embed experience
  • Still reasonably narrow
  • Works well with nonce-based CSPs

Cons

  • You’re trusting third-party JavaScript, not just framed content
  • Script behavior may change without notice
  • Some setups end up needing extra img-src or connect-src allowances

My take

This is where most production sites land. It’s acceptable, but I treat third-party script allowances as a real security decision, not a checkbox.

Sometimes the embed script or rendered content pulls from more than www.instagram.com. Teams then start adding subdomains or related CDN hosts.

That can look like this:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://www.instagram.com https://*.instagram.com;
  frame-src 'self' https://www.instagram.com https://*.instagram.com;
  img-src 'self' data: https: https://*.cdninstagram.com https://*.instagram.com;
  connect-src 'self' https://www.instagram.com https://*.instagram.com;
  style-src 'self';
  object-src 'none';
  base-uri 'self';
  form-action 'self';

Pros

  • Fewer random production breakages
  • More resilient to vendor-side hostname changes
  • Easier for teams that don’t want to babysit CSP reports every week

Cons

  • Noticeably weaker than single-origin allowlists
  • Wildcards are easy to overuse
  • You may end up permitting way more than the embed actually needs

When it makes sense

If you’ve verified that Instagram is legitimately using multiple subdomains in your environment, this is practical. I’d still keep it scoped to the directives that need it. Don’t copy the wildcard into default-src just because it’s convenient.

Option 4: The lazy fix — broad https: allowances

You’ll see policies like this in the wild:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https:;
  frame-src 'self' https:;
  img-src 'self' data: https:;
  style-src 'self' 'unsafe-inline';

Yes, the embed will probably work.

Pros

  • Fast
  • Low maintenance
  • Console errors go away quickly

Cons

  • Barely a meaningful CSP anymore
  • Any HTTPS third-party script or frame is now fair game
  • Makes incident response and policy review much harder

My opinion

I hate this pattern. It turns CSP into theater. If your only goal is “stop browser warnings,” fine, but call it what it is: a compatibility policy, not a security policy.

Option 5: Isolate embeds on a dedicated page or subdomain

If Instagram embeds are business-critical and you don’t want to pollute your main app policy, isolate them.

For example:

  • www.example.com keeps a strict CSP
  • embeds.example.com gets a more permissive CSP for social widgets

Main app:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-r4nd0m' 'strict-dynamic';
  style-src 'self';
  img-src 'self' data:;
  connect-src 'self' https://api.example.com;
  frame-ancestors 'none';
  object-src 'none';
  base-uri 'self';

Embed page:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://www.instagram.com;
  frame-src 'self' https://www.instagram.com;
  img-src 'self' data: https: https://*.instagram.com https://*.cdninstagram.com;
  connect-src 'self' https://www.instagram.com https://*.instagram.com;
  style-src 'self' 'unsafe-inline';
  object-src 'none';
  base-uri 'self';

Pros

  • Strongest separation of risk
  • Keeps your main application clean
  • Easier to audit and explain

Cons

  • More infrastructure and deployment complexity
  • More moving parts for cookies, routing, and styling
  • Can be overkill for small sites

When I’d choose it

For larger apps, multi-team environments, or anything handling sensitive user workflows, this is often the best architecture. Third-party widgets are messy. Giving them their own sandboxed home is a sane move.

Directives that usually matter most

If you need a refresher on directive behavior, https://csp-guide.com is useful, and the canonical reference is the MDN CSP documentation.

A few practical notes:

frame-src

If the embed renders inside an iframe, this is usually the first thing that breaks.

frame-src 'self' https://www.instagram.com;

script-src

Needed if you load Instagram’s embed JavaScript.

script-src 'self' 'nonce-r4nd0m' https://www.instagram.com;

If you already use 'strict-dynamic', be careful. A nonce-bearing bootstrap script can change how host allowlists are interpreted in modern browsers. Don’t cargo-cult that combination without understanding it.

img-src

This one gets overlooked. Instagram embeds often need remote images.

A lot of production policies already do this, like the headertest.com example:

img-src 'self' data: https:;

That’s convenient, but broad. If you want tighter control, prefer explicit hosts once you know them.

connect-src

Sometimes required for background requests, telemetry, or API fetches made by the embed script.

connect-src 'self' https://www.instagram.com https://*.instagram.com;

A practical rollout pattern

This is the least painful way I’ve found to ship third-party embeds under CSP:

  1. Start with your current strict policy.
  2. Add only frame-src for Instagram.
  3. Test.
  4. If using embed.js, add script-src for the exact host.
  5. Watch DevTools and CSP reports for blocked img-src and connect-src.
  6. Add the narrowest host allowances that fix real breakage.
  7. Re-test in multiple browsers.

If you support legacy browsers, expect weirdness. CSP behavior is much better than it used to be, but third-party embed code still finds creative ways to be annoying.

If you’re using Instagram’s official embed script and want a balanced policy, I’d start here:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-r4nd0m' https://www.instagram.com;
  frame-src 'self' https://www.instagram.com;
  img-src 'self' data: https:;
  connect-src 'self' https://www.instagram.com;
  style-src 'self';
  object-src 'none';
  base-uri 'self';
  form-action 'self';
  frame-ancestors 'none';

Then tighten img-src and connect-src later if your reporting data shows the exact hosts in use.

That’s the real comparison here:

  • Tight single-origin policy: best security, more maintenance
  • Broader wildcard policy: easier operations, weaker trust boundary
  • Dedicated embed origin: best long-term design, more complexity
  • https: everywhere: fast, but mostly security cosplay

If I had to pick one for a serious production app, I’d choose either the narrow allowlist or a dedicated embed origin. Instagram embeds are not special enough to justify throwing away a good CSP.