Adding a Content Security Policy (CSP) is one of the most effective ways to reduce cross-site scripting (XSS) risk and control which resources a browser can load.
This guide explains how to add content security policy headers, start with a safe reporting setup, and tighten rules without breaking your site.
What Content Security Policy does
Content Security Policy is a browser security standard that tells user agents which sources are allowed for scripts, styles, images, fonts, frames, and other resource types.
It is implemented through the Content-Security-Policy HTTP response header, or in limited cases with a <meta> tag.
A well-designed CSP can reduce the impact of injected JavaScript, block malicious inline code, and limit data exfiltration paths.
It is not a replacement for input validation, output encoding, or secure development practices, but it adds a strong browser-enforced control layer.
How to add content security policy in practice
The safest way to add CSP is to begin in monitoring mode, observe violations, and then move to enforcement.
In production, the policy is usually delivered as an HTTP response header by the web server, reverse proxy, CDN, or application framework.
Step 1: Inventory the resources your page needs
Before writing a policy, list the domains and resource types your application actually uses.
Include first-party assets, analytics providers, payment gateways, CDN endpoints, font services, API calls, and embedded tools such as video players or chat widgets.
Typical categories to review include:
- Scripts: application bundles, third-party libraries, tag managers
- Styles: CSS files, inline styles generated by frameworks
- Images: local assets, data URIs, CDN-hosted media
- Fonts: self-hosted fonts or external font providers
- Connections: fetch, XHR, WebSocket, EventSource endpoints
- Frames: embedded content from trusted services
Step 2: Start with a report-only policy
A report-only policy lets the browser log violations without blocking content.
This is useful because many modern sites depend on inline code, dynamic script loading, or legacy third-party assets.
Example report-only header:
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self'; report-to csp-endpoint;
With this approach, you can collect violation reports, identify unexpected dependencies, and refine the policy before enforcement.
Many teams use both a report-only policy and a strict enforced policy during rollout.
Step 3: Define a restrictive baseline
Start from a baseline that blocks everything by default, then allow only what is needed.
The directive default-src 'self' is a common starting point, but many applications require more granular rules.
A simple enforced policy might look like this:
Content-Security-Policy: default-src 'self'; img-src 'self' data: https:; font-src 'self' https://fonts.gstatic.com; style-src 'self' https://fonts.googleapis.com; script-src 'self'; connect-src 'self' https://api.example.com; frame-src https://www.youtube.com;
This example shows how CSP can separate trust by resource type.
The goal is to avoid broad allowances such as * or large vendor domains when smaller, specific sources are possible.
Key directives you should understand
default-src
default-src acts as a fallback for many resource types when a more specific directive is missing.
It is often the first directive to define because it sets the overall trust posture.
script-src
script-src is one of the most important directives because JavaScript is the primary XSS target.
Prefer nonces or hashes for inline scripts and avoid 'unsafe-inline' unless you are temporarily supporting legacy code.
If your site uses modern build pipelines, nonces are often the easiest long-term solution.
Each response gets a unique nonce, and only scripts carrying that nonce are permitted to execute.
style-src
style-src controls CSS loading.
Frameworks and component libraries sometimes rely on inline styles, so you may need to move styles to external files or use hashes where appropriate.
img-src
img-src governs images, including logos, avatars, and tracking pixels.
If your site uses embedded base64 images, include data: only where necessary.
connect-src
connect-src limits outbound requests made by fetch, XMLHttpRequest, WebSocket, and similar APIs.
This directive is useful for reducing unauthorized data access and controlling where client-side code can communicate.
frame-src and frame-ancestors
frame-src restricts what your page can embed, while frame-ancestors controls who can embed your page.
The latter helps defend against clickjacking and is often paired with CSP as a modern alternative to older X-Frame-Options-only approaches.
How to deploy CSP through common platforms
Apache
In Apache, CSP is commonly added with Header set inside the virtual host or site configuration:
Header set Content-Security-Policy "default-src 'self'; script-src 'self'"
Nginx
In Nginx, use the add_header directive in the server or location block:
add_header Content-Security-Policy "default-src 'self'; script-src 'self'" always;
Node.js and Express
Many Node.js applications use the Helmet middleware, which can generate CSP headers and help you manage directives programmatically.
Helmet is especially useful when policies need to vary by route or environment.
CDNs and reverse proxies
Cloudflare, Fastly, AWS CloudFront, and similar platforms can inject or modify security headers at the edge.
This is helpful when you want a consistent policy across multiple application servers without changing every codebase.
How to handle inline scripts and styles safely
Inline code is one of the most common reasons CSP breaks during rollout.
The modern best practice is to remove inline JavaScript where possible and replace it with external files or nonced script tags.
For cases where inline code cannot be eliminated, use:
- Nonces: random per-response values attached to trusted script or style tags
- Hashes: SHA-based digests of exact inline content
- Trusted Types: a browser feature that reduces DOM XSS risk in supported environments
Avoid using 'unsafe-inline' as a long-term fix.
It weakens the policy substantially and allows inline execution broadly, which undermines the main value of CSP.
How to test and monitor violations
Testing CSP requires both browser checks and server-side monitoring.
Use the browser developer console to inspect blocked resources, then collect violation reports in your logging or observability system.
Useful practices include:
- Reviewing reports for legitimate assets accidentally blocked
- Separating development and production policies
- Validating changes on high-traffic pages first
- Tracking violations after releases to catch regressions
When you see violations, determine whether the source should be allowed, moved, or removed.
In many cases, the right fix is to eliminate unnecessary third-party dependencies rather than expand the policy.
Common mistakes when adding CSP
- Allowing overly broad sources such as
*or entire CDN domains without review - Using
'unsafe-inline'and'unsafe-eval'in production without a migration plan - Forgetting subresources such as fonts, frames, or API endpoints
- Deploying enforcement before collecting violation data
- Ignoring mobile app webviews, staging environments, or localized domains
Another frequent issue is assuming one CSP fits every page.
Complex applications often need route-specific policies for login pages, dashboards, documentation sites, and marketing pages.
What a strong CSP rollout looks like
A mature CSP rollout usually includes a report-only phase, a strict production policy, regular audits of third-party services, and periodic updates as the application changes.
Teams that treat CSP as a living control rather than a one-time header tend to get the most security value.
For most organizations, the best path is to begin with a minimal policy, measure violations, reduce inline dependencies, and enforce only the sources that are truly necessary.
That approach keeps the policy maintainable while still delivering meaningful protection against browser-based attacks.