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-Onlyif 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 typesscript-src: JavaScript sourcesstyle-src: CSS sourcesimg-src: images and pixelsfont-src: web fontsconnect-src: XHR, fetch, WebSocket, EventSourceframe-src: embedded iframesworker-src: Web Workers and Service Workersmanifest-src: web app manifestobject-src: plugins, usually set to'none'base-uri: controls<base>form-action: where forms can submitframe-ancestors: who can embed your siteupgrade-insecure-requests: upgradeshttp://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-ancestorsis preferred overX-Frame-Optionsfor modern control.object-src 'none'should be nearly universal.base-uri 'self'orbase-uri 'none'is easy hardening.- Meta tag CSP is weaker than an HTTP response header.
report-uriis legacy-ish in some setups; many teams now preferreport-towhere 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-srcandmanifest-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'
Recommended Testing Flow
- Deploy a
Report-Onlypolicy. - Browse critical user flows.
- Review blocked sources.
- Remove anything unnecessary.
- Convert to enforced
Content-Security-Policy. - Re-test with headertest.com and browser DevTools.
- 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.