CSP Examples Cookbook: Copy-Paste Security Headers

Content Security Policy (CSP) is still one of the highest-impact browser defenses you can deploy in 2026. A good CSP reduces XSS risk, limits third-party script abuse, narrows data exfiltration paths, and makes supply-chain mistakes less catastrophic.

The hard part is not the syntax. The hard part is shipping a policy that matches your stack.

This cookbook gives you complete, copy-paste-ready CSP examples for common servers, frameworks, hosting platforms, and integrations. Each example is short, practical, and designed to be adapted with minimal changes.

Use headertest.com to validate your headers and spot common mistakes.

Before You Paste Anything

A few rules make CSP much easier to manage:

  • Start with Content-Security-Policy-Report-Only if you are unsure.
  • Prefer nonces or hashes over 'unsafe-inline'.
  • Avoid 'unsafe-eval' unless a framework truly needs it in development.
  • Keep default-src 'self' as a baseline.
  • Add only the sources you actually use.
  • Separate development and production policies.
  • If you use third-party tags, document why each domain is allowed.
  • Test with real pages, not just your homepage.

A Good Starting Mental Model

Most policies are built from these directives:

  • default-src: fallback for most fetch types
  • script-src: JavaScript sources
  • style-src: CSS sources
  • img-src: images and pixels
  • font-src: web fonts
  • connect-src: XHR, fetch, WebSocket, EventSource
  • frame-src: embedded iframes
  • worker-src: Web Workers and Service Workers
  • manifest-src: web app manifest
  • object-src: plugins, usually set to 'none'
  • base-uri: controls <base>
  • form-action: where forms can submit
  • frame-ancestors: who can embed your site
  • upgrade-insecure-requests: upgrades http:// subresources to HTTPS

Three Reusable Baselines

These are useful templates you can adapt everywhere.

Strict CSP

Best for apps you control fully and can nonce or hash inline code.

Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-{RANDOM_NONCE}' 'strict-dynamic'; style-src 'self' 'nonce-{RANDOM_NONCE}'; img-src 'self' data: blob:; font-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; worker-src 'self' blob:; manifest-src 'self'; upgrade-insecure-requests

Moderate CSP

Good for many production sites using a few CDNs and some inline styles.

Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.jsdelivr.net https://www.googletagmanager.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: blob: https://www.google-analytics.com; font-src 'self' https://fonts.gstatic.com data:; connect-src 'self' https://www.google-analytics.com https://region1.google-analytics.com; frame-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'; upgrade-insecure-requests

Permissive CSP

Only for legacy apps while migrating.

Content-Security-Policy: default-src 'self' https: data: blob: 'unsafe-inline' 'unsafe-eval'; script-src 'self' https: 'unsafe-inline' 'unsafe-eval'; style-src 'self' https: 'unsafe-inline'; img-src 'self' https: data: blob:; font-src 'self' https: data:; connect-src 'self' https: wss:; frame-src 'self' https:; object-src 'none'; base-uri 'self'; form-action 'self' https:; frame-ancestors 'self'

Nginx CSP Examples

Nginx Strict

server {
    listen 443 ssl http2;
    server_name example.com;
    root /var/www/example;

    set $csp_nonce $request_id;

    add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'nonce-$csp_nonce' 'strict-dynamic'; style-src 'self' 'nonce-$csp_nonce'; img-src 'self' data: blob:; font-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; worker-src 'self' blob:; manifest-src 'self'; upgrade-insecure-requests" always;

    location / {
        try_files $uri $uri/ /index.html;
    }
}

Use a real cryptographically random nonce in app code if possible. $request_id is convenient, but application-generated nonces are better.

Nginx Moderate

server {
    listen 443 ssl http2;
    server_name example.com;
    root /var/www/example;

    add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://cdn.jsdelivr.net https://www.googletagmanager.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: blob: https://www.google-analytics.com; font-src 'self' https://fonts.gstatic.com data:; connect-src 'self' https://www.google-analytics.com https://region1.google-analytics.com; frame-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'; upgrade-insecure-requests" always;

    location / {
        try_files $uri $uri/ /index.html;
    }
}

Nginx Permissive

server {
    listen 443 ssl http2;
    server_name legacy.example.com;
    root /var/www/legacy;

    add_header Content-Security-Policy "default-src 'self' https: data: blob: 'unsafe-inline' 'unsafe-eval'; script-src 'self' https: 'unsafe-inline' 'unsafe-eval'; style-src 'self' https: 'unsafe-inline'; img-src 'self' https: data: blob:; font-src 'self' https: data:; connect-src 'self' https: wss:; frame-src 'self' https:; object-src 'none'; base-uri 'self'; form-action 'self' https:; frame-ancestors 'self'" always;

    location / {
        try_files $uri $uri/ /index.html;
    }
}

Nginx Report-Only

server {
    listen 443 ssl http2;
    server_name example.com;

    add_header Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; report-uri https://csp-report.example.com/report" always;
}

Apache CSP Examples

Apache .htaccess

<IfModule mod_headers.c>
    Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'; upgrade-insecure-requests"
</IfModule>

Apache VirtualHost Strict

<VirtualHost *:443>
    ServerName example.com
    DocumentRoot /var/www/example/public

    Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'nonce-REPLACE_AT_RUNTIME' 'strict-dynamic'; style-src 'self' 'nonce-REPLACE_AT_RUNTIME'; img-src 'self' data: blob:; font-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; worker-src 'self' blob:; manifest-src 'self'; upgrade-insecure-requests"
</VirtualHost>

Apache Report-Only

<IfModule mod_headers.c>
    Header always set Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; object-src 'none'; report-uri https://csp-report.example.com/report"
</IfModule>

Cloudflare CSP Examples

Cloudflare Transform Rule Header Value

Set a response header named Content-Security-Policy with this value:

default-src 'self'; script-src 'self' https://www.googletagmanager.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: blob: https://www.google-analytics.com; font-src 'self' https://fonts.gstatic.com data:; connect-src 'self' https://www.google-analytics.com https://region1.google-analytics.com; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'; upgrade-insecure-requests

Cloudflare Worker Example

export default {
  async fetch(request, env, ctx) {
    const response = await fetch(request);
    const newResponse = new Response(response.body, response);

    newResponse.headers.set(
      "Content-Security-Policy",
      "default-src 'self'; script-src 'self' https://www.googletagmanager.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: blob: https://www.google-analytics.com; font-src 'self' https://fonts.gstatic.com data:; connect-src 'self' https://www.google-analytics.com https://region1.google-analytics.com; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'; upgrade-insecure-requests"
    );

    return newResponse;
  }
};

Vercel and Netlify

Vercel vercel.json

{
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        {
          "key": "Content-Security-Policy",
          "value": "default-src 'self'; script-src 'self' https://www.googletagmanager.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: blob: https://www.google-analytics.com; font-src 'self' https://fonts.gstatic.com data:; connect-src 'self' https://www.google-analytics.com https://region1.google-analytics.com; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'; upgrade-insecure-requests"
        }
      ]
    }
  ]
}

Netlify _headers

/*
  Content-Security-Policy: default-src 'self'; script-src 'self' https://www.googletagmanager.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: blob: https://www.google-analytics.com; font-src 'self' https://fonts.gstatic.com data:; connect-src 'self' https://www.google-analytics.com https://region1.google-analytics.com; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'; upgrade-insecure-requests

React, Next.js, and Vite

React Static App public/_headers for Netlify

/*
  Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' https://api.example.com; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'

Next.js next.config.js

const csp = `
  default-src 'self';
  script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.googletagmanager.com;
  style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
  img-src 'self' data: blob: https://www.google-analytics.com;
  font-src 'self' https://fonts.gstatic.com data:;
  connect-src 'self' https://www.google-analytics.com https://region1.google-analytics.com;
  frame-src 'self';
  object-src 'none';
  base-uri 'self';
  form-action 'self';
  frame-ancestors 'self';
  upgrade-insecure-requests;
`.replace(/\n/g, '');

module.exports = {
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          {
            key: 'Content-Security-Policy',
            value: csp
          }
        ]
      }
    ];
  }
};

This example is practical for many Next.js apps. Tighten it for production if you can remove inline and eval allowances.

Vite with Express Backend

import express from 'express';

const app = express();

app.use((req, res, next) => {
  res.setHeader(
    'Content-Security-Policy',
    "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' ws://localhost:5173 http://localhost:5173 https://api.example.com; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'"
  );
  next();
});

app.use(express.static('dist'));

app.listen(3000);

For Vite development, ws://localhost:5173 is usually required for HMR.


Vue and Nuxt

Vue CLI / Static Hosting

<!doctype html>
<html lang="en">
<head>
  <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' https://api.example.com; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'">
  <meta charset="utf-8">
  <title>Vue App</title>
</head>
<body>
  <div id="app"></div>
  <script src="/assets/index.js"></script>
</body>
</html>

HTTP headers are better than meta tags, but this is useful for plain static hosting.

Nuxt 3 nuxt.config.ts

export default defineNuxtConfig({
  routeRules: {
    '/**': {
      headers: {
        'Content-Security-Policy': "default-src 'self'; script-src 'self' 'unsafe-inline' https://www.googletagmanager.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: blob: https://www.google-analytics.com; font-src 'self' https://fonts.gstatic.com data:; connect-src 'self' https://www.google-analytics.com https://region1.google-analytics.com https://api.example.com; frame-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'; upgrade-insecure-requests"
      }
    }
  }
});

Angular

Angular with Nginx

server {
    listen 443 ssl http2;
    server_name app.example.com;
    root /var/www/angular-app/browser;

    add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' https://api.example.com; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'; worker-src 'self' blob:;" always;

    location / {
        try_files $uri $uri/ /index.html;
    }
}

Angular Development Friendly Policy


Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' ws://localhost:4200 http://localhost:4200 https://api.example.com; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'

Some Angular development setups still need 'unsafe-eval'. Keep it out of production if possible.


Svelte and SvelteKit

SvelteKit hooks.server.ts

import type { Handle } from '@sveltejs/kit';

export const handle: Handle = async ({ event, resolve }) => {
  const response = await resolve(event);

  response.headers.set(
    'Content-Security-Policy',
    "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' https://api.example.com; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'; worker-src 'self' blob:;"
  );

  return response;
};

Static Svelte App

<!doctype html>
<html lang="en">
<head>
  <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' https://api.example.com; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'">
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>Svelte App</title>
</head>
<body>
  <script defer src="/build/bundle.js"></script>
</body>
</html>

Django and Flask

Django settings.py

SECURE_CONTENT_TYPE_NOSNIFF = True

CSP_DEFAULT_SRC = ("'self'",)
CSP_SCRIPT_SRC = ("'self'", "https://www.googletagmanager.com")
CSP_STYLE_SRC = ("'self'", "'unsafe-inline'", "https://fonts.googleapis.com")
CSP_IMG_SRC = ("'self'", "data:", "blob:", "https://www.google-analytics.com")
CSP_FONT_SRC = ("'self'", "data:", "https://fonts.gstatic.com")
CSP_CONNECT_SRC = ("'self'", "https://www.google-analytics.com", "https://region1.google-analytics.com", "https://api.example.com")
CSP_OBJECT_SRC = ("'none'",)
CSP_BASE_URI = ("'self'",)
CSP_FORM_ACTION = ("'self'",)
CSP_FRAME_ANCESTORS = ("'self'",)
CSP_UPGRADE_INSECURE_REQUESTS = True

This assumes you use a Django CSP package such as django-csp.

Flask

from flask import Flask, make_response, render_template_string

app = Flask(__name__)

CSP = (
    "default-src 'self'; "
    "script-src 'self' https://www.googletagmanager.com; "
    "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; "
    "img-src 'self' data: blob: https://www.google-analytics.com; "
    "font-src 'self' data: https://fonts.gstatic.com; "
    "connect-src 'self' https://www.google-analytics.com https://region1.google-analytics.com https://api.example.com; "
    "object-src 'none'; "
    "base-uri 'self'; "
    "form-action 'self'; "
    "frame-ancestors 'self'; "
    "upgrade-insecure-requests"
)

@app.route("/")
def home():
    response = make_response(render_template_string("<h1>Hello</h1>"))
    response.headers["Content-Security-Policy"] = CSP
    return response

if __name__ == "__main__":
    app.run()

Rails

Rails Application Controller

class ApplicationController < ActionController::Base
  after_action :set_csp

  private

  def set_csp
    response.set_header(
      "Content-Security-Policy",
      "default-src 'self'; " \
      "script-src 'self' https://www.googletagmanager.com; " \
      "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; " \
      "img-src 'self' data: blob: https://www.google-analytics.com; " \
      "font-src 'self' data: https://fonts.gstatic.com; " \
      "connect-src 'self' https://www.google-analytics.com https://region1.google-analytics.com https://api.example.com; " \
      "object-src 'none'; " \
      "base-uri 'self'; " \
      "form-action 'self'; " \
      "frame-ancestors 'self'; " \
      "upgrade-insecure-requests"
    )
  end
end

Rails Initializer

Rails.application.config.content_security_policy do |policy|
  policy.default_src :self
  policy.script_src  :self, "https://www.googletagmanager.com"
  policy.style_src   :self, :unsafe_inline, "https://fonts.googleapis.com"
  policy.img_src     :self, :data, :blob, "https://www.google-analytics.com"
  policy.font_src    :self, :data, "https://fonts.gstatic.com"
  policy.connect_src :self, "https://www.google-analytics.com", "https://region1.google-analytics.com", "https://api.example.com"
  policy.object_src  :none
  policy.base_uri    :self
  policy.form_action :self
  policy.frame_ancestors :self
  policy.upgrade_insecure_requests true
end

Laravel

Laravel Middleware

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class ContentSecurityPolicy
{
    public function handle(Request $request, Closure $next): Response
    {
        $response = $next($request);

        $response->headers->set(
            'Content-Security-Policy',
            "default-src 'self'; " .
            "script-src 'self' https://www.googletagmanager.com; " .
            "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; " .
            "img-src 'self' data: blob: https://www.google-analytics.com; " .
            "font-src 'self' data: https://fonts.gstatic.com; " .
            "connect-src 'self' https://www.google-analytics.com https://region1.google-analytics.com https://api.example.com; " .
            "object-src 'none'; " .
            "base-uri 'self'; " .
            "form-action 'self'; " .
            "frame-ancestors 'self'; " .
            "upgrade-insecure-requests"
        );

        return $response;
    }
}

Register the middleware in app/Http/Kernel.php.


Express

Express with Helmet

import express from 'express';
import helmet from 'helmet';

const app = express();

app.use(
  helmet({
    contentSecurityPolicy: {
      useDefaults: false,
      directives: {
        defaultSrc: ["'self'"],
        scriptSrc: ["'self'", "https://www.googletagmanager.com"],
        styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"],
        imgSrc: ["'self'", "data:", "blob:", "https://www.google-analytics.com"],
        fontSrc: ["'self'", "data:", "https://fonts.gstatic.com"],
        connectSrc: ["'self'", "https://www.google-analytics.com", "https://region1.google-analytics.com", "https://api.example.com"],
        frameSrc: ["'self'"],
        objectSrc: ["'none'"],
        baseUri: ["'self'"],
        formAction: ["'self'"],
        frameAncestors: ["'self'"],
        workerSrc: ["'self'", "blob:"],
        upgradeInsecureRequests: []
      }
    }
  })
);

app.get('/', (req, res) => {
  res.send('<h1>Hello</h1>');
});

app.listen(3000);

Express Manual Header

const express = require('express');
const app = express();

app.use((req, res, next) => {
  res.setHeader(
    'Content-Security-Policy',
    "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' https://api.example.com wss://ws.example.com; frame-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'; worker-src 'self' blob:;"
  );
  next();
});

app.listen(3000);

WordPress

Apache .htaccess for WordPress

<IfModule mod_headers.c>
    Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.googletagmanager.com https://www.google-analytics.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: blob: https://www.google-analytics.com; font-src 'self' data: https://fonts.gstatic.com; connect-src 'self' https://www.google-analytics.com https://region1.google-analytics.com; frame-src 'self' https://www.youtube.com https://www.youtube-nocookie.com; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'; upgrade-insecure-requests"
</IfModule>

WordPress themes and plugins often force a more permissive policy. Audit plugin output before tightening.

WordPress PHP Header

<?php
add_action('send_headers', function () {
    header("Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.googletagmanager.com https://www.google-analytics.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: blob: https://www.google-analytics.com; font-src 'self' data: https://fonts.gstatic.com; connect-src 'self' https://www.google-analytics.com https://region1.google-analytics.com; frame-src 'self' https://www.youtube.com https://www.youtube-nocookie.com; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'; upgrade-insecure-requests");
});

Add to your theme functions.php or a custom plugin.


Shopify

Shopify storefronts are constrained by the platform, so custom CSP control is limited unless you are fronting the store with a reverse proxy or using Hydrogen.

Shopify via Reverse Proxy

server {
    listen 443 ssl http2;
    server_name shop.example.com;

    location / {
        proxy_pass https://your-store.myshopify.com;
        proxy_set_header Host your-store.myshopify.com;

        add_header Content-Security-Policy "default-src 'self' https://cdn.shopify.com https://*.shopify.com; script-src 'self' 'unsafe-inline' https://cdn.shopify.com https://*.shopify.com https://www.googletagmanager.com; style-src 'self' 'unsafe-inline' https://cdn.shopify.com https://fonts.googleapis.com; img-src 'self' data: blob: https://cdn.shopify.com https://*.shopify.com; font-src 'self' data: https://fonts.gstatic.com https://cdn.shopify.com; connect-src 'self' https://*.shopify.com https://monorail-edge.shopifysvc.com https://www.google-analytics.com; frame-src 'self' https://*.shopify.com https://js.stripe.com https://www.paypal.com; object-src 'none'; base-uri 'self'; form-action 'self' https://*.shopify.com; frame-ancestors 'self'; upgrade-insecure-requests" always;
    }
}

Shopify Hydrogen on Vercel

{
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        {
          "key": "Content-Security-Policy",
          "value": "default-src 'self' https://cdn.shopify.com https://*.shopify.com; script-src 'self' https://cdn.shopify.com https://*.shopify.com https://www.googletagmanager.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: blob: https://cdn.shopify.com https://*.shopify.com; font-src 'self' data: https://fonts.gstatic.com; connect-src 'self' https://*.shopify.com https://monorail-edge.shopifysvc.com https://www.google-analytics.com; frame-src 'self' https://*.shopify.com; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'; upgrade-insecure-requests"
        }
      ]
    }
  ]
}

Static HTML

Static HTML with Meta Tag

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>Static Site</title>
</head>
<body>
  <h1>Hello</h1>
  <script src="/app.js"></script>
</body>
</html>

Static HTML with Full Security Header on Nginx

server {
    listen 443 ssl http2;
    server_name static.example.com;
    root /var/www/static;

    add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data: blob:; font-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; upgrade-insecure-requests" always;
}

Astro

Astro with astro.config.mjs and Netlify Header File

public/_headers

/*
  Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' https://api.example.com; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'

Astro on Vercel

{
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        {
          "key": "Content-Security-Policy",
          "value": "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' https://api.example.com; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'"
        }
      ]
    }
  ]
}

Google Analytics and Google Tag Manager

These services often break strict policies unless you explicitly allow their endpoints.

GA4 + GTM CSP

Content-Security-Policy: default-src 'self'; script-src 'self' https://www.googletagmanager.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://www.google-analytics.com; connect-src 'self' https://www.google-analytics.com https://region1.google-analytics.com https://stats.g.doubleclick.net; frame-src https://www.googletagmanager.com; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'; upgrade-insecure-requests

GTM with Inline Bootstrap Snippet

Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' https://www.googletagmanager.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://www.google-analytics.com; connect-src 'self' https://www.google-analytics.com https://region1.google-analytics.com https://stats.g.doubleclick.net; frame-src https://www.googletagmanager.com; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'

If you can, replace inline GTM bootstrapping with a nonce or hash.


Stripe and PayPal

Payment flows usually need scripts, frames, and API endpoints.

Stripe CSP

Content-Security-Policy: default-src 'self'; script-src 'self' https://js.stripe.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https://*.stripe.com; connect-src 'self' https://api.stripe.com https://*.stripe.com; frame-src 'self' https://js.stripe.com https://hooks.stripe.com https://*.stripe.com; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'; upgrade-insecure-requests

PayPal CSP

Content-Security-Policy: default-src 'self'; script-src 'self' https://www.paypal.com https://www.paypalobjects.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https://www.paypal.com https://www.paypalobjects.com; connect-src 'self' https://www.paypal.com https://www.sandbox.paypal.com; frame-src 'self' https://www.paypal.com https://www.sandbox.paypal.com; object-src 'none'; base-uri 'self'; form-action 'self' https://www.paypal.com https://www.sandbox.paypal.com; frame-ancestors 'self'; upgrade-insecure-requests

Stripe + PayPal Combined

Content-Security-Policy: default-src 'self'; script-src 'self' https://js.stripe.com https://www.paypal.com https://www.paypalobjects.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https://*.stripe.com https://www.paypal.com https://www.paypalobjects.com; connect-src 'self' https://api.stripe.com https://*.stripe.com https://www.paypal.com https://www.sandbox.paypal.com; frame-src 'self' https://js.stripe.com https://hooks.stripe.com https://*.stripe.com https://www.paypal.com https://www.sandbox.paypal.com; object-src 'none'; base-uri 'self'; form-action 'self' https://www.paypal.com https://www.sandbox.paypal.com; frame-ancestors 'self'; upgrade-insecure-requests

CDNs

Site Using jsDelivr and unpkg

Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.jsdelivr.net https://unpkg.com; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; img-src 'self' data: blob: https://cdn.jsdelivr.net; font-src 'self' data: https://cdn.jsdelivr.net; connect-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'; upgrade-insecure-requests

Site Using Cloudinary and Fonts

Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: blob: https://res.cloudinary.com; font-src 'self' data: https://fonts.gstatic.com; connect-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'; upgrade-insecure-requests

Email Templates

CSP has very limited relevance in email clients because many clients strip scripts entirely and do not honor headers the same way browsers do for web pages. Still, if you host a “view in browser” version, secure that page.

Browser View for Email Archive

server {
    listen 443 ssl http2;
    server_name mail.example.com;
    root /var/www/email-archive;

    add_header Content-Security-Policy "default-src 'self'; script-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data: https:; connect-src 'none'; object-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; upgrade-insecure-requests" always;
}

Static Email Archive HTML

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data: https:; connect-src 'none'; object-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'">
  <title>Email Archive</title>
</head>
<body>
  <table role="presentation" width="100%">
    <tr><td>Hello</td></tr>
  </table>
</body>
</html>

Browser Extensions

Extensions use different CSP models depending on browser and manifest version. Browser extension CSP is not the same as website CSP.

Chrome Extension Manifest V3

{
  "manifest_version": 3,
  "name": "Secure Extension",
  "version": "1.0.0",
  "action": {
    "default_popup": "popup.html"
  },
  "background": {
    "service_worker": "background.js"
  },
  "permissions": [],
  "host_permissions": ["https://api.example.com/*"],
  "content_security_policy": {
    "extension_pages": "script-src 'self'; object-src 'none'; base-uri 'self';"
  }
}

Firefox Extension Style

{
  "manifest_version": 2,
  "name": "Secure Extension",
  "version": "1.0.0",
  "browser_action": {
    "default_popup": "popup.html"
  },
  "background": {
    "scripts": ["background.js"]
  },
  "content_security_policy": "script-src 'self'; object-src 'none';"
}

Check current browser extension docs before shipping, because extension CSP rules can differ significantly from regular web CSP and change over time.


WebSockets

If your app uses WebSockets, add the endpoint to connect-src.

Production WebSocket CSP

Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' https://api.example.com wss://ws.example.com; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'; worker-src 'self' blob:;

Local Development WebSocket CSP

Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' http://localhost:3000 ws://localhost:3000 ws://localhost:5173; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'

Service Workers

Service Workers commonly need worker-src and sometimes broader connect-src.

Service Worker Friendly CSP

Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' https://api.example.com; worker-src 'self' blob:; manifest-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'; upgrade-insecure-requests

PWA CSP with Manifest and Push

Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' https://api.example.com https://fcm.googleapis.com; worker-src 'self' blob:; manifest-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'; upgrade-insecure-requests

Copy-Paste Nonce Example

When you want a strict CSP, nonces are usually the cleanest path.

Express Nonce Example

import crypto from 'crypto';
import express from 'express';

const app = express();

app.use((req, res, next) => {
  const nonce = crypto.randomBytes(16).toString('base64');
  res.locals.nonce = nonce;

  res.setHeader(
    'Content-Security-Policy',
    `default-src 'self'; script-src 'self' 'nonce-${nonce}' 'strict-dynamic'; style-src 'self' 'nonce-${nonce}'; img-src 'self' data: blob:; font-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; worker-src 'self' blob:; manifest-src 'self'; upgrade-insecure-requests`
  );

  next();
});

app.get('/', (req, res) => {
  res.send(`
    <!doctype html>
    <html>
      <head>
        <style nonce="${res.locals.nonce}">
          body { font-family: sans-serif; }
        </style>
      </head>
      <body>
        <h1>Hello</h1>
        <script nonce="${res.locals.nonce}">
          console.log('nonce-protected inline script');
        </script>
      </body>
    </html>
  `);
});

app.listen(3000);

Common CSP Patterns

Block Everything Except Same-Origin


Content-Security-Policy: default-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'

Allow Images from Anywhere but Scripts Only from Self


Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src * data: blob:; font-src 'self' data:; connect-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'

Embedded App That Must Be Framed by Parent Domain


Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self'; frame-ancestors https://app.example.com https://admin.example.com; object-src 'none'; base-uri 'self'; form-action 'self'

API-Heavy SPA


Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' https://api.example.com https://uploads.example.com wss://ws.example.com; worker-src 'self' blob:; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'; manifest-src 'self'

Practical Notes for 2026

  • frame-ancestors is preferred over X-Frame-Options for modern control.
  • object-src 'none' should be nearly universal.
  • base-uri 'self' or base-uri 'none' is easy hardening.
  • Meta tag CSP is weaker than an HTTP response header.
  • report-uri is legacy-ish in some setups; many teams now prefer report-to where supported, but compatibility and reporting infrastructure vary.
  • Development servers often need looser connect-src, ws:, and sometimes 'unsafe-eval'.
  • Third-party tools change domains. Re-check vendor docs before freezing a policy.
  • A CSP that is too broad can create false confidence. A CSP that is too strict can break revenue-critical flows. Test login, checkout, analytics, embeds, search, and admin pages.

Minimal Reporting Example


Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; report-uri https://csp-report.example.com/report

report-to Example


Report-To: {"group":"csp-endpoint","max_age":10886400,"endpoints":[{"url":"https://csp-report.example.com/report"}]}
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; report-to csp-endpoint

Quick Selection Guide

Use this if you want the fastest safe-ish starting point:

  • Static site with no third parties: strict same-origin policy
  • SPA with API calls: add API domains to connect-src
  • Site with GA/GTM: allow Google Tag Manager and Analytics endpoints
  • Checkout with Stripe/PayPal: allow script, frame, and connect domains for payment providers
  • PWA: add worker-src and manifest-src
  • Vite/Next/Nuxt dev: allow localhost WebSockets and possibly 'unsafe-eval'
  • WordPress: start permissive, then tighten after plugin audit
  • Shopify: use platform-aware policy or reverse proxy approach
  • Email archive pages: script-src 'none'
  1. Deploy a Report-Only policy.
  2. Browse critical user flows.
  3. Review blocked sources.
  4. Remove anything unnecessary.
  5. Convert to enforced Content-Security-Policy.
  6. Re-test with headertest.com and browser DevTools.
  7. Re-run after adding any new vendor, widget, or tag.

A strong CSP is not one giant universal string. It is a living allowlist shaped by your actual application. The examples above are meant to get you from zero to working fast, with enough structure to tighten over time.