--- description: How to set a Content Security Policy in Express with Helmet, including per-request nonces and report-only mode. --- # Express Express has no opinion about security headers, so almost everyone reaches for [Helmet](https://helmet.js.org/), which sets a stack of headers including a default CSP. That default is deliberately strict, and it is usually the first thing that breaks an existing app - which is exactly why you want report-only first. ## What Helmet gives you - A default `Content-Security-Policy` applied as soon as you `app.use(helmet())`, including `default-src 'self'`, `script-src 'self'`, `object-src 'none'` and `upgrade-insecure-requests`. - Custom directives passed as a nested `directives` object. - Directive values that can be **functions** receiving the request and response, which is how per-request nonces get in. - A `reportOnly` flag that swaps the header for `Content-Security-Policy-Report-Only`. ## Setting the header Your URL comes from the [Setup](https://report-uri.com/account/setup/) page: ```js app.use( helmet({ contentSecurityPolicy: { reportOnly: true, directives: { "default-src": ["'self'"], "script-src": ["'self'"], "report-uri": ["https://abc123.report-uri.com/r/d/csp/reportOnly"], }, }, }), ); ``` ## Adding a nonce Generate the nonce before Helmet runs, then read it back in a directive function: ```js import crypto from 'node:crypto' app.use((req, res, next) => { res.locals.nonce = crypto.randomBytes(16).toString('base64') next() }) app.use( helmet({ contentSecurityPolicy: { directives: { "script-src": ["'self'", (req, res) => `'nonce-${res.locals.nonce}'`], }, }, }), ) ``` Then render `nonce="<%= nonce %>"` on your inline scripts. ## Things to watch for - Custom `directives` **replace** Helmet's defaults for the directives you name, and Helmet merges the rest. Check the header you actually emit rather than assuming your object is additive. - Middleware order matters: the nonce middleware has to run before Helmet, or the directive function reads `undefined`. - Helmet's defaults change between major versions. Pin the version and re-check the emitted header after an upgrade, rather than discovering it from production reports. ## Official documentation [Helmet - Content Security Policy](https://helmet.js.org/) ## Start Monitoring with Report URI **Already using us for CSP?** Find your report-uri value on [Setup](https://report-uri.com/account/setup/), or view your [CSP reports](https://report-uri.com/account/reports/csp/). **New to Report URI?** Create an account and grab your report-uri value from the [Setup](https://report-uri.com/account/setup/) page. [Start your free trial](https://report-uri.com/register/?plan=starter2025)