CSP Examples Cookbook: Copy-Paste Security Headers
Table of Contents
🔧 Validate Your CSP Policy
Copy a CSP example, deploy it, then verify it works with a real scan.
Verify with headertest.com →
🔧 Validate Your CSP Policy
Copy a CSP example, deploy it, then verify it works with a real scan.
Verify with headertest.com →🔧 Validate Your CSP Policy
Copy a CSP example, deploy it, then verify it works with a real scan.
Verify with headertest.com →🔧 Validate Your CSP Policy
Copy a CSP example, deploy it, then verify it works with a real scan.
Verify with headertest.com →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.