Content Security Policy (CSP) Header Examples and Best Practices
Real-world CSP header examples for common web app scenarios. Covers directive reference, nonces, hashes, reporting, and how to set up CSP in Next.js without breaking your app.
Content Security Policy (CSP) is one of the most powerful browser security mechanisms available — and one of the most frequently misconfigured. A correctly deployed CSP eliminates entire classes of XSS attacks. A misconfigured one either does nothing or breaks your application.
This guide provides real-world CSP examples, explains every directive, and walks through implementing CSP in modern web apps without breaking anything.
What CSP Does
CSP is an HTTP header that tells the browser which sources it's allowed to load content from. If a script, style, image, or other resource doesn't come from an approved source, the browser blocks it.
Without CSP, a successful XSS injection can execute arbitrary JavaScript in the victim's browser context. With a strict CSP, even if an attacker injects <script>, the browser refuses to execute it.
The Most Important Directive: default-src
default-src is the fallback for all other resource types. If you only set one directive, this is it:
Content-Security-Policy: default-src 'self'
This allows resources only from your own origin. Any other directive that isn't explicitly set falls back to default-src.
Complete CSP Directive Reference
| Directive | Controls |
|---|---|
default-src | Fallback for all types |
script-src | <script>, JavaScript |
style-src | <style>, CSS |
img-src | <img>, background images |
font-src | Web fonts |
connect-src | fetch, XHR, WebSocket, EventSource |
media-src | <audio>, <video> |
object-src | <object>, <embed>, <applet> |
child-src | Workers, iframes |
frame-src | <iframe> sources |
worker-src | Web Workers |
frame-ancestors | Who can embed this page |
form-action | Where <form> can submit |
base-uri | Allowed <base> href values |
upgrade-insecure-requests | Upgrade HTTP resources to HTTPS |
block-all-mixed-content | Block HTTP resources on HTTPS pages |
report-uri / report-to | Where to send violation reports |
CSP Values
| Value | Meaning |
|---|---|
'self' | Same origin (scheme + host + port) |
'none' | Block everything |
'unsafe-inline' | Allow inline scripts/styles (weakens CSP significantly) |
'unsafe-eval' | Allow eval() and similar (weakens CSP) |
'nonce-{value}' | Allow specific inline script with matching nonce |
'sha256-{hash}' | Allow specific inline script by its hash |
https: | Any HTTPS source |
data: | data: URIs |
blob: | blob: URIs |
example.com | Specific hostname |
*.example.com | All subdomains |
Real-World CSP Examples
Strict CSP (recommended for new apps)
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-{RANDOM_NONCE}';
style-src 'self' 'nonce-{RANDOM_NONCE}';
img-src 'self' data: https:;
font-src 'self';
connect-src 'self' https://api.yourdomain.com;
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests
This uses nonces to allow inline scripts/styles without 'unsafe-inline'. The nonce must be unique per request and unpredictable (use crypto.randomUUID() or similar).
Next.js App with Analytics and CDN
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-{NONCE}' https://analytics.yourdomain.com;
style-src 'self' 'nonce-{NONCE}';
img-src 'self' data: https://images.unsplash.com https://avatars.githubusercontent.com;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://api.yourdomain.com https://analytics.yourdomain.com;
object-src 'none';
base-uri 'self';
frame-ancestors 'none'
SaaS App with Stripe and Intercom
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-{NONCE}' https://js.stripe.com https://widget.intercom.io;
style-src 'self' 'nonce-{NONCE}';
img-src 'self' data: https: blob:;
font-src 'self' data:;
connect-src 'self' https://api.stripe.com https://api-iam.intercom.io wss://nexus-websocket-a.intercom.io https://yourdomain.com;
frame-src https://js.stripe.com https://hooks.stripe.com;
object-src 'none';
base-uri 'self'
Legacy App (with inline scripts — less secure)
If you have a legacy app you can't immediately refactor to use nonces:
Content-Security-Policy:
default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
object-src 'none';
base-uri 'self'
This still provides value by blocking external script loading, even without restricting inline scripts. Treat it as a starting point, not a final state.
Nonces in Practice
A nonce (number used once) is a cryptographically random value added to your CSP header and your <script> tags. Browsers allow scripts with matching nonces, blocking everything else:
<!-- CSP header: script-src 'nonce-abc123' -->
<script nonce="abc123">
// This executes
</script>
<script>
// This is BLOCKED
</script>
Implementing Nonces in Next.js
// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
export function middleware(request: NextRequest) {
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}';
img-src 'self' data: https:;
object-src 'none';
base-uri 'self';
frame-ancestors 'none'
`.replace(/\s+/g, ' ').trim();
const response = NextResponse.next();
response.headers.set('Content-Security-Policy', csp);
response.headers.set('x-nonce', nonce);
return response;
}
Then use the nonce in your layout:
// app/layout.tsx
import { headers } from 'next/headers';
export default async function RootLayout({ children }) {
const nonce = (await headers()).get('x-nonce') ?? '';
return (
<html>
<head>
<script nonce={nonce} src="/scripts/analytics.js" />
</head>
<body>{children}</body>
</html>
);
}
CSP Reporting
The most useful feature for deployment: report violations without blocking anything.
Content-Security-Policy-Report-Only:
default-src 'self';
script-src 'self' 'nonce-{NONCE}';
report-uri /api/csp-report
Report-Only mode sends violation reports without enforcing anything. Use this before going live with enforcement to discover what your CSP would break.
Your /api/csp-report endpoint receives POST requests with JSON bodies like:
{
"csp-report": {
"document-uri": "https://yourdomain.com/app",
"violated-directive": "script-src",
"blocked-uri": "https://cdn.example.com/analytics.js",
"original-policy": "default-src 'self'; script-src 'self'"
}
}
Testing Your CSP
- browser DevTools → Console tab: CSP violations are logged with details
- Report-Only mode + monitor your report endpoint for violations
- CSP Evaluator (Google): paste your CSP and get a security analysis
- ShipSafer: automated CSP testing against your live domain with actionable suggestions