Content Security Policy Headers Setup Guide for 2026
April 3, 2026 · Security, Auth, Web Security
Content Security Policy (CSP) is still the single most effective browser-side control for preventing cross-site scripting (XSS) and limiting the blast radius of injected content. In 2026, browsers enforce CSP consistently, tooling is better, and most frameworks now expose hooks for nonces and hashes. The challenge is not what CSP is, but how to roll it out without breaking your app.
This guide walks through a practical, production-safe CSP setup: from a report-only rollout to a locked-down policy with nonces and hashes. You’ll see ready-to-use header examples for Nginx, Apache, Node.js, and common CDNs, plus debugging and testing workflows that developers actually use.
What CSP does and why it matters in 2026
CSP tells the browser what sources are allowed for scripts, styles, images, fonts, frames, and network calls. If content violates the policy, the browser blocks it (or reports it in report-only mode). That means:
- Injected scripts are blocked unless they match a nonce, hash, or allowed source.
- Exfiltration is constrained because fetch/XHR/WebSocket destinations are limited by connect-src.
- Risky features are disabled (e.g., object-src set to none, base-uri locked).
CSP complements server-side validation and encoding. It doesn’t replace sanitization or output escaping—but it is a strong last line of defense that browsers enforce regardless of your code path.
Start with report-only mode (safe rollout)
Report-only lets you collect violations without breaking production traffic. In 2026, a safe rollout is:
- Deploy CSP-Report-Only for 1–2 weeks.
- Inspect reports and identify inline scripts, third-party widgets, and unexpected domains.
- Convert to enforcement once the report noise is below 1% of page loads.
Example report-only header:
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.example.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self' https://api.example.com; report-to csp-endpoint;
Note the temporary 'unsafe-inline'—that is typical in report-only while you migrate to nonces or hashes.
Design a baseline policy (secure defaults)
A baseline CSP for a modern web app should include at least these directives:
- default-src 'self' as the baseline allowlist.
- script-src with nonces or hashes, never 'unsafe-inline' in enforcement mode.
- style-src with nonces or hashes if you can; otherwise keep inline styles minimal.
- connect-src locked to your API and analytics endpoints.
- img-src including data: only if needed for SVG placeholders or inlined assets.
- object-src 'none' to disable legacy plugins.
- base-uri 'self' to prevent base tag abuse.
- frame-ancestors 'none' unless your app must be embedded.
Example enforcement CSP:
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-{{NONCE}}' https://cdn.example.com; style-src 'self' 'nonce-{{NONCE}}'; img-src 'self' data: https://images.example.com; connect-src 'self' https://api.example.com https://events.example.com; font-src 'self' https://fonts.example.com; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; upgrade-insecure-requests; block-all-mixed-content;
upgrade-insecure-requests and block-all-mixed-content are still valuable when you’re forcing HTTPS across legacy assets.
Nonce vs hash: choosing the right strategy
Nonces are random per-response tokens added to script and style tags. They are ideal when you can generate HTML server-side.
Hashes are SHA-256/384/512 of the inline script or style content. They work well for static inline snippets (e.g., a tiny bootloader).
Rules of thumb:
- Use nonces for most dynamic apps (SSR or template rendering).
- Use hashes for static HTML pages or inline critical CSS.
- Avoid 'unsafe-inline' in enforcement mode.
Nonce example (Node.js / Express)
import crypto from 'node:crypto';
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}'; style-src 'self' 'nonce-${nonce}'; object-src 'none'; base-uri 'self';`
);
next();
});
Then in your template:
<script nonce="{{nonce}}">
window.__BOOTSTRAP__ = {...};
</script>
Hash example (static HTML)
Content-Security-Policy: default-src 'self'; script-src 'self' 'sha256-2fNnP4v5mH6Z9d0Qxq...='; style-src 'self'; object-src 'none';
Compute the hash with a one-liner:
printf "console.log('hi');" | openssl dgst -sha256 -binary | openssl base64
Real-world CSP headers for popular stacks
Nginx
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'nonce-$request_id'; style-src 'self' 'nonce-$request_id'; img-src 'self' data:; connect-src 'self' https://api.example.com; object-src 'none'; base-uri 'self'; frame-ancestors 'none'" always;
Note: Nginx cannot generate true random nonces per response without additional modules. Use an app-layer nonce or a nonce from your reverse proxy if supported.
Apache
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'nonce-%{UNIQUE_ID}e'; style-src 'self' 'nonce-%{UNIQUE_ID}e'; object-src 'none'; base-uri 'self'"
Node.js (Helmet)
import helmet from 'helmet';
app.use((req, res, next) => {
res.locals.nonce = crypto.randomBytes(16).toString('base64');
next();
});
app.use(
helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", (req, res) => `'nonce-${res.locals.nonce}'`],
styleSrc: ["'self'", (req, res) => `'nonce-${res.locals.nonce}'`],
imgSrc: ["'self'", 'data:'],
objectSrc: ["'none'"],
baseUri: ["'self'"],
frameAncestors: ["'none'"]
}
})
);
Reporting: collect violations and fix fast
In 2026, the recommended way is the Reporting API using report-to. You can still use the legacy report-uri for compatibility.
Report-To: {"group":"csp-endpoint","max_age":10886400,"endpoints":[{"url":"https://csp.example.com/report"}]}
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' 'unsafe-inline'; report-to csp-endpoint;
Many teams store reports as JSON and analyze them. If you need to quickly inspect the payload, paste the JSON into the DevToolKit JSON Formatter: ../tools/json-formatter.html
Common CSP breakages (and how to solve them)
- Inline scripts blocked: Add a nonce or hash and remove 'unsafe-inline'.
- Third-party widgets: Add their script domains to script-src and their XHR endpoints to connect-src.
- Images from a CDN: Add the CDN to img-src.
- Fonts failing: Add font origin to font-src and ensure CORS headers are correct.
- Unexpected eval usage: Remove 'unsafe-eval' and replace eval-based code. Most bundlers no longer need it in production.
If you need to quickly test a regex for matching allowed subdomains before updating CSP, use the DevToolKit Regex Tester: ../tools/regex-tester.html
Recommended CSP for SPAs
Single-page apps typically load static bundles and make API calls. A hardened CSP example:
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'nonce-{{NONCE}}'; img-src 'self' data: https://images.example.com; connect-src 'self' https://api.example.com https://analytics.example.com; font-src 'self' https://fonts.example.com; object-src 'none'; base-uri 'self'; frame-ancestors 'none';
Most modern SPA bundles don’t need inline scripts at all. That lets you skip nonce for scripts and only use it for inline styles if necessary.
Testing workflow that developers actually use
- Step 1: Enable report-only CSP in staging and production.
- Step 2: Aggregate reports by directive and blocked URI.
- Step 3: Fix true positives and eliminate 'unsafe-inline'.
- Step 4: Ship enforcement CSP and monitor error rate for 48 hours.
When you need to encode a report URL into a JSON config or query string, use DevToolKit’s URL Encoder/Decoder: ../tools/url-encoder.html
Should you use CSP nonces or CSP hashes?
In 2026, use nonces for server-rendered apps and hashes for static HTML. If you are shipping a pure SPA with external bundles and no inline script, you can skip both and still be secure. The only time you should consider 'unsafe-inline' in enforcement mode is when you have no control over inline scripts and can’t add nonces or hashes—which is rare and risky.
Checklist: minimum CSP for production
- Default: default-src 'self'
- Scripts: script-src 'self' 'nonce-...' or hashes
- Styles: style-src 'self' 'nonce-...' or hashes
- Networking: connect-src locked to APIs and analytics
- Frames: frame-ancestors set to 'none' unless needed
- Plugins: object-src 'none'
- Base tag: base-uri 'self'
Final thoughts
A CSP rollout is a security project and a dev-experience project. The safest way is to start in report-only mode, resolve violations, then enforce. In 2026, a well-designed CSP is table stakes for any app that handles auth, payments, or user-generated content. If you implement nonces or hashes and keep your allowlist tight, you’ll eliminate a massive class of XSS attacks with minimal ongoing maintenance.
FAQ
Do I need CSP if I already sanitize user input?
Yes, you still need CSP because sanitization can fail and CSP blocks injected scripts even if a bug slips through.
Is report-only CSP safe for production?
Yes, report-only CSP is safe in production because it does not block content and only generates violation reports.
Should I allow 'unsafe-inline' in 2026?
No, you should not allow 'unsafe-inline' in enforcement mode because it defeats CSP’s primary defense against XSS.
Are CSP nonces compatible with CDNs?
Yes, CSP nonces work with CDNs as long as the nonce is generated per response and injected into the HTML before it is cached or personalized.
What’s the fastest way to debug CSP violations?
The fastest way is to enable report-only mode and inspect aggregated reports by directive and blocked URI, then tighten the policy iteratively.
Recommended Tools & Resources
Level up your workflow with these developer tools:
Auth0 → Cloudflare Zero Trust → Web Application Security by Andrew Hoffman →Dev Tools Digest
Get weekly developer tools, tips, and tutorials. Join our developer newsletter.