Skip to content

Security Headers

Content Security Policy (CSP) explained — a safe way to roll it out

What a Content-Security-Policy header does, why it stops XSS, how to roll it out in report-only mode first, and copy-paste examples for nginx, Apache and Netlify.

By SurfaceWatch··6 min read

If an attacker manages to slip a <script> tag into one of your pages (cross-site scripting, or XSS), the browser normally runs it, because it can't tell your code from theirs. Content-Security-Policy (CSP) is a response header that tells the browser which sources of scripts, styles, images and frames are allowed. Anything else is blocked.

It is the strongest defence against XSS you can add with a header, and also the one most often skipped, because a careless policy can break your own site. This guide shows a safe way in.

What a CSP looks like

A policy is a list of directives, each naming a type of content and where it may come from:

Content-Security-Policy: default-src 'self'; img-src 'self' data:; script-src 'self'; frame-ancestors 'none'
  • default-src 'self': by default, only load things from your own domain.
  • script-src: where JavaScript may come from. This is the directive that stops XSS.
  • img-src, style-src, font-src, connect-src: the same idea for other content types.
  • frame-ancestors 'none': nobody may embed your site in a frame (this also covers clickjacking).

Step 1: start in report-only mode

The safe way to roll CSP out is the Content-Security-Policy-Report-Only header. The browser doesn't block anything. It only logs what would have been blocked in the developer console, so you can fix your policy before it can break anything.

Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' https://www.googletagmanager.com

Browse your site with the console open, note every violation, and add the sources you really use.

Step 2: avoid 'unsafe-inline' where you can

Allowing 'unsafe-inline' in script-src lets inline scripts run, which throws away most of the XSS protection. Better options are to move scripts into files, or to use a per-response nonce (script-src 'nonce-abc123') or a hash for the few inline scripts you need to keep.

Step 3: enforce it

When the report-only logs are clean for a week or so, rename the header to Content-Security-Policy. Keep the report-only version running with the next stricter policy, to tighten it step by step.

Copy-paste examples

nginx

add_header Content-Security-Policy "default-src 'self'; frame-ancestors 'none'" always;

Apache

Header always set Content-Security-Policy "default-src 'self'; frame-ancestors 'none'"

Netlify (_headers file)

/*
  Content-Security-Policy: default-src 'self'; frame-ancestors 'none'

Treat these as starting points and add the sources your site really needs (analytics, fonts, payment widgets) before enforcing.

How to check whether your site has one

In a terminal, curl -sI https://yourdomain.com | grep -i content-security shows the header if it is set. SurfaceWatch checks it as part of its one-click security grade, flags a missing or weak policy, and gives you a ready-to-paste fix for your platform.

A CSP is a safety net, not a substitute for escaping user input. Use both. Start in report-only mode, and tighten gradually.

Keep reading

Check your site's CSP — add SurfaceWatch free

One click, an honest grade, and the exact fix. Free to install. No account needed.