CSP (Content Security Policy): Setup Guide from Scratch
Content Security Policy (Content Security Policy) is an HTTP response header that tells the browser which sources of scripts, styles, images, fonts and other assets are legitimate. A properly authored CSP turns an XSS vulnerability into an incident log entry instead of a full session takeover. This guide covers the entire rollout — from Report-Only to strict strict-dynamic with nonces.
How CSP works
The browser reads the Content-Security-Policy header and, for every fetch directive (script-src, style-src, img-src, font-src, etc.), checks whether the origin is permitted. Disallowed resources are blocked and, if report-uri or report-to is set, a JSON CSP report is sent. The spec lives at W3C CSP Level 3.
Core directives
default-src is the fallback for every fetch directive; script-src gates scripts; style-src — stylesheets; img-src — images; connect-src — fetch/XHR/WebSocket; font-src — fonts; frame-src — iframes; frame-ancestors — the modern X-Frame-Options; object-src — plugin objects (use 'none'); base-uri — the <base> element (use 'self'); form-action — form targets.
Source expressions: 'self', nonce, hash, strict-dynamic
'self'— the current origin'nonce-RANDOM'— one-shot random value per request; scripts carryingnonce="RANDOM"are allowed'sha256-HASH'— allows the inline script matching this SHA-256 hash'strict-dynamic'— a nonce/hash-trusted script can load additional scripts; simplifies bundler output'unsafe-inline','unsafe-eval'— avoid in production
Example of a strict policy
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-{NONCE}' 'strict-dynamic';
style-src 'self' 'nonce-{NONCE}';
img-src 'self' data: https:;
font-src 'self';
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
object-src 'none';
upgrade-insecure-requests
A nonce must be 128 bits of cryptographically random data generated per request. Reusing one nonce across an SPA template is equivalent to 'unsafe-inline'.
Report-Only mode for debugging
Before enforcing, run the policy in report-only:
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report; report-to csp-endpoint
Collect reports for 2–4 weeks, whitelist legitimate sources, drop dead ones, and only flip to enforce once the logs go quiet.
nginx + nonce (with ngx_http_sub_module)
set_secure_random_alphanum $cspNonce 32;
sub_filter_once off;
sub_filter 'NONCE_PLACEHOLDER' $cspNonce;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'nonce-$cspNonce' 'strict-dynamic'; style-src 'self' 'nonce-$cspNonce'; object-src 'none'; base-uri 'self'" always;
Next.js 15 middleware
import { NextResponse } from 'next/server';
export function middleware(request) {
const nonce = Buffer.from(crypto.randomUUID()).toString('base64');
const csp = `default-src 'self'; script-src 'self' 'nonce-${nonce}' 'strict-dynamic'; style-src 'self' 'nonce-${nonce}'; object-src 'none'; base-uri 'self'`;
const headers = new Headers(request.headers);
headers.set('x-nonce', nonce);
const res = NextResponse.next({ request: { headers } });
res.headers.set('Content-Security-Policy', csp);
return res;
}
Verification and reporting
Check the policy: curl -I https://example.com | grep -i content-security or use the enterno.io CSP Analyzer — it parses directives, flags missing ones and assigns an A–F grade. Related headers live in HTTP Security Headers.
FAQ
Google Analytics broke — why? Add https://www.googletagmanager.com to script-src and https://www.google-analytics.com to connect-src.
What about inline style="..."? CSP3 supports 'unsafe-hashes' for attributes, but externalising the CSS is always better.
strict-dynamic instead of hosts? Yes — it ignores the script host-allowlist and trusts only nonces/hashes, which makes long-term maintenance painless.
Can CSP ride in a <meta> tag? Technically yes, but frame-ancestors and report-uri are ignored. Fallback only.
Conclusion
CSP rollout strategy: 1) audit resources, 2) enable Report-Only, 3) after 2–4 weeks switch to enforce, 4) add monitoring on header changes, 5) validate with the CSP Analyzer. See also: XSS protection, SRI for CDN.