How to create a Content Security Policy step by step
Learn how to create a Content Security Policy from scratch. This guide covers CSP directives, common configurations by site type, testing in report-only mode, and production deployment.
What is Content Security Policy and why you need it#
Content Security Policy is a browser security mechanism that gives website owners fine-grained control over which resources can load and execute on their pages. Delivered as an HTTP response header, CSP tells the browser exactly where scripts, stylesheets, images, and other assets are allowed to come from. If a resource does not match the policy, the browser blocks it before it ever runs.
Cross-site scripting remains one of the most common vulnerabilities on the web. Attackers inject malicious JavaScript into pages through form fields, URL parameters, or compromised third-party scripts. A well-crafted CSP neutralizes the vast majority of these attacks because the injected code will not appear on the approved source list. Even if an attacker finds an injection point, the browser refuses to execute the payload.
Despite its effectiveness, many teams skip CSP because writing one from scratch feels tedious and error-prone. That is exactly the problem our generator solves. You can scan your website first to see what is missing, then jump into the generator to create a policy that fits your stack.
- Blocks unauthorized scripts from executing, stopping most XSS attacks at the browser level
- Prevents data exfiltration by restricting which domains your page can contact
- Mitigates clickjacking when combined with the frame-ancestors directive
- Provides violation reports so you can monitor attempted attacks in real time
- Improves your security posture score and demonstrates compliance with modern standards
Understanding CSP directives and their roles#
A Content Security Policy is built from a series of directives. Each directive controls a specific type of resource. You separate directives with semicolons and list the approved sources after the directive name. When the browser encounters a resource request, it checks the matching directive. If no specific directive exists for that resource type, the browser falls back to default-src.
Getting familiar with the most important directives before using the generator will help you make better decisions. You do not need to include every directive in every policy. Focus on the ones that matter for your application, then tighten the rest through default-src. For a deeper dive into every directive and how our scanner evaluates them, see our complete CSP reference guide.
Key directives to know
default-srcsets the fallback policy for any resource type that does not have its own directive. Setting this to'self'or'none'is the foundation of a strong policy.script-srccontrols where JavaScript can be loaded from. This is the most critical directive for XSS prevention. Avoid'unsafe-inline'and'unsafe-eval'whenever possible.style-srccontrols where stylesheets can come from. Many CSS-in-JS libraries require'unsafe-inline', but nonces or hashes are preferred.connect-srcrestricts the URLs your page can contact via fetch, XMLHttpRequest, WebSocket, and EventSource. Essential for preventing data exfiltration.worker-srccontrols where Web Workers, Service Workers, and Shared Workers can be loaded from. Falls back tochild-src, thenscript-src, thendefault-src.frame-ancestorsdetermines which sites can embed your page in an iframe. Setting this to'none'replaces X-Frame-Options and prevents clickjacking. Only supports'self','none', host sources, and scheme sources.upgrade-insecure-requestsinstructs the browser to upgrade all HTTP requests to HTTPS automatically. This directive takes no source values.
Nonces, hashes, and modern CSP keywords#
The biggest challenge with CSP is allowing inline scripts and styles without resorting to 'unsafe-inline', which effectively disables XSS protection. CSP Level 2 and 3 introduced two powerful mechanisms to solve this: nonces and hashes. These let you allowlist specific inline code blocks while blocking everything else.
Nonces
A nonce is a random, single-use token generated on each server request. You include the nonce in both the CSP header and the script or style tag's nonce attribute. The browser compares the two values: if they match, the inline code runs; if not, it is blocked. Because the nonce changes on every request, an attacker cannot predict or reuse it.
# The CSP header includes the nonce
Content-Security-Policy: script-src 'self' 'nonce-R4nd0mB4se64=='
# In your HTML, every inline script must include the matching nonce
<script nonce="R4nd0mB4se64==">
// This script runs because the nonce matches
</script>
<script>
// This script is BLOCKED because it has no nonce
</script>The nonce must be cryptographically random (at least 128 bits), base64 encoded, and regenerated on every page load. Never hardcode the same nonce across requests. Our CSP generator includes a "Generate nonce" button that creates a random nonce value you can use as a template.
Hashes
A hash allowlists a specific inline script or style by its content. You compute a SHA-256, SHA-384, or SHA-512 hash of the exact code, base64 encode it, and add it to the CSP header. If the inline code matches the hash, it runs. Any modification to the code (even a whitespace change) will produce a different hash and cause the browser to block it.
# Hash of the exact script content
Content-Security-Policy: script-src 'self' 'sha256-qznLcsROx4GACP2dm0UCKCzCG+HiZ1guq6ZZDob/Tng='
# This exact script matches the hash and will execute
<script>alert('Hello');</script>Hashes are ideal when your inline code is static and does not change between deployments. For dynamic inline code that changes per request, nonces are the better choice. The CSP generator includes an "Add hash" button where you can paste a precomputed hash value.
strict-dynamic
The 'strict-dynamic' keyword extends trust from a nonce or hash protected script to any scripts that script loads dynamically. If your main application script is loaded via nonce, it can use document.createElement('script') to load additional scripts, and those will also be trusted without needing their own nonce.
When 'strict-dynamic' is present, the browser ignores host allowlists, scheme sources like https:, and 'unsafe-inline' in the same directive. This means you can safely include both for backward compatibility with older browsers: modern browsers use the nonce and strict-dynamic, while older browsers fall back to the host allowlist.
script-src 'nonce-abc123' 'strict-dynamic' https://cdn.example.com 'unsafe-inline'Other modern keywords
'wasm-unsafe-eval'allows WebAssembly compilation (WebAssembly.compile and instantiate) without enabling generaleval(). If your application uses WASM but not eval(), this is much safer than'unsafe-eval'.'unsafe-hashes'allows specific inline event handlers (likeonclick) or style attributes whose content matches a hash in the policy. This is more targeted than'unsafe-inline'because only the exact matching code runs.'report-sample'instructs the browser to include the first 40 characters of any violating script or style in CSP violation reports. This makes debugging much easier.
How to use the CSP generator step by step#
Our CSP generator walks you through policy creation without requiring you to memorize directive syntax. You make choices in a visual interface and the tool assembles a valid header value you can copy straight into your server configuration. To get started, open the CSP generator in a new tab and follow along.
First, choose your base strictness level. The generator offers a starting template that sets default-src to 'self' by default. From there, toggle individual directives on or off depending on what your application needs. For each enabled directive, you can add trusted sources by typing a domain or choosing from common presets like Google Fonts, Google Analytics, or popular CDNs.
Second, review each directive's source list carefully. The generator highlights potentially risky values like 'unsafe-inline' and 'unsafe-eval' so you can weigh the trade-off between compatibility and security. If your application supports nonces, the tool can generate a nonce placeholder for you.
Third, decide whether to deploy in enforcement mode or report-only mode. Report-only is the safer choice when you are rolling out a new policy. It logs violations without blocking anything, so you can verify the policy will not break legitimate functionality. Once you are satisfied, switch to enforcement mode. The final output is a complete header value ready to paste.
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'nonce-abc123'; img-src 'self' data: https:; connect-src 'self' https://api.example.com; frame-ancestors 'none'; object-src 'none'; base-uri 'self'Common CSP configurations for different site types#
There is no single CSP that works for every website. The resources your pages load depend on your tech stack, third-party integrations, and how you serve assets. Below are practical starting points for the most common site architectures. Adapt them to your own domain names and CDN URLs, then refine based on violation reports.
For a comprehensive reference on every directive value and how browsers interpret them, MDN's CSP documentation is an excellent resource. You can also review our CSP analysis guide to understand how we score and evaluate policies.
Static sites
Static sites built with tools like Hugo, Jekyll, or plain HTML typically load all assets from the same origin. They rarely need inline scripts or eval. This makes them ideal candidates for a very strict policy.
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self'; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'Single-page applications
SPAs built with React, Vue, or Angular often rely on API calls, WebSocket connections, and sometimes inline styles injected by CSS-in-JS libraries. You need to allow your API domain in connect-src and may need to accommodate style injection. Using nonces for inline styles is the best approach if your framework supports it. Adding 'strict-dynamic' to script-src lets your main nonce-protected script dynamically load additional scripts (like chunk splits) without listing each one.
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-{random}' 'strict-dynamic'; style-src 'self' 'nonce-{random}'; img-src 'self' data: https:; connect-src 'self' https://api.yourapp.com wss://realtime.yourapp.com; font-src 'self' https://fonts.gstatic.com; worker-src 'self'; frame-ancestors 'none'; object-src 'none'; base-uri 'self'; upgrade-insecure-requestsWordPress and CMS sites
CMS platforms are more challenging because plugins and themes frequently inject inline scripts and styles. You may need to allow 'unsafe-inline' for styles initially, then work toward removing it by auditing your plugins. Start with a permissive report-only policy and tighten over time.
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: https:; font-src 'self' https://fonts.gstatic.com; connect-src 'self' https://www.google-analytics.com; frame-ancestors 'self'; object-src 'none'; base-uri 'self'Testing and debugging with report-only mode#
The Content-Security-Policy-Report-Only header is your best friend when rolling out a new policy. It behaves exactly like the enforcing header, except it only reports violations instead of blocking them. Your users see no difference in functionality while you collect data about what would break under the real policy.
You can run report-only and enforcing headers simultaneously. This is useful when you have an existing policy in enforcement and want to test a stricter one alongside it. Pair the report-only header with a report-uri or report-to directive so violations are sent to a logging endpoint you can monitor.
Do not deploy an untested policy to production
An overly restrictive CSP can break your site's JavaScript, CSS, images, and API calls. Always run in report-only mode for at least a few days and review the violation logs before switching to enforcement. Broken functionality on a live site is worse than having no CSP at all.
- Open your browser's developer console and filter for CSP warnings to see violations in real time
- Test every major page and user flow, not just the homepage, because different pages may load different resources
- Check third-party embeds like payment forms, chat widgets, and analytics scripts, which often load resources from unexpected domains
- Use a reporting service or your own logging endpoint to aggregate violations across all users, not just your own browser
- Look for browser extensions that trigger false violations; these are normal and can be ignored in your reports
Deploying CSP in production#
The recommended rollout strategy follows three phases. First, deploy the header as Content-Security-Policy-Report-Only and monitor for a week or more. Second, fix any legitimate violations by updating the policy or refactoring code that relies on inline scripts. Third, switch the header name to Content-Security-Policy to enforce it. Keep the reporting directive active so you continue to catch new violations as your site evolves.
How you add the header depends on your server. Below are configuration examples for the three most common setups. In all cases, the header should be set on every HTML response. It does not need to be set on static assets like images or CSS files, though it will not cause harm if it is.
Apache
# .htaccess or httpd.conf
Header always set Content-Security-Policy \
"default-src 'self'; \
script-src 'self'; \
style-src 'self'; \
img-src 'self' data:; \
frame-ancestors 'none'; \
object-src 'none'; \
base-uri 'self'"Nginx
# nginx.conf or site configuration
add_header Content-Security-Policy
"default-src 'self';
script-src 'self';
style-src 'self';
img-src 'self' data:;
frame-ancestors 'none';
object-src 'none';
base-uri 'self'" always;Express (Node.js)
// Express middleware
app.use((req, res, next) => {
res.setHeader(
'Content-Security-Policy',
"default-src 'self'; " +
"script-src 'self'; " +
"style-src 'self'; " +
"img-src 'self' data:; " +
"frame-ancestors 'none'; " +
"object-src 'none'; " +
"base-uri 'self'"
);
next();
});- Keep your policy in a single place (environment variable or config file) so you can update it without redeploying application code
- Version control your CSP alongside your infrastructure configuration
- Schedule periodic reviews of your policy whenever you add new third-party scripts or change your hosting setup
- Use the Helmet middleware for Express or similar libraries that manage security headers as a group
Continue reading
Ready to build your Content Security Policy?
Generate a production-ready Content Security Policy in minutes with the CSP builder tool.