Back to all articles
Implement

CSP Nonce: What It Is and How to Use Nonce-Based Content Security Policy

A CSP nonce is a per-response token that lets a single trusted script run without weakening your Content-Security-Policy. Here is how to use it correctly.

By CSPify Team 6 min read

Nonces unlock dynamic frontends without unsafe-inline.

  • Generate a fresh nonce per response with a CSPRNG.
  • Wire the nonce into both the CSP header and the script tag.
  • Combine nonces with strict-dynamic to scale to dynamic loaders.
  • Avoid CDN caching gotchas that break nonce-based CSPs.

When building a modern web application, relying on a traditional host-based allowlist for your Content Security Policy often collapses under the weight of dynamic frontends. If you have ever tried to maintain a massive list of domains, only to find that your tag manager or analytics script injects yet another untracked inline script, you know the pain. A CSP nonce solves this problem by shifting trust from where a script comes from to a cryptographic token that proves the script was authorized by your server.

Instead of resorting to ‘unsafe-inline’, which completely disables inline script protection and leaves your application vulnerable to Cross-Site Scripting (XSS), a nonce-based CSP allows you to execute specific, trusted inline scripts securely. If you are struggling to build a policy from scratch, you can use a CSP generator to scaffold your initial headers, but understanding how nonces work is critical for securing modern stacks. By implementing a nonce, you create a robust defense mechanism that adapts to the dynamic nature of contemporary web development without sacrificing security.

What a CSP nonce is

The one-line definition

A CSP nonce is a random, single-use, base64-encoded string generated by your server for each HTTP response, which is placed in both the Content-Security-Policy header and the nonce attribute of trusted HTML tags. When the browser parses the HTML, it compares the nonce attribute on the script tag with the nonce declared in the HTTP header. If they match, the script is allowed to execute; if they don’t, the script is blocked and a CSP violation is reported.

What “nonce” stands for in cryptography

In cryptography, a “nonce” stands for a number used once. It is a value that must be unique for a given context and must never be reused. In the context of Content Security Policy, the “context” is a single HTTP response. By generating a fresh, unpredictable token for every page load, you ensure that an attacker cannot guess the nonce and use it to execute malicious injected scripts. If an attacker manages to inject a script tag into your page via an XSS vulnerability, they won’t know the correct nonce for that specific page load, and the browser will refuse to execute their malicious payload. This makes nonces an incredibly powerful tool for mitigating the impact of injection vulnerabilities.

When nonce-based CSP beats an allowlist

Traditional CSPs rely on allowlists—listing every domain that is allowed to serve scripts. While this works for simple, static websites, it quickly becomes unmanageable for complex applications. Managing an allowlist requires constant vigilance, as developers frequently add new third-party services, and those services often change their own content delivery networks. Here is when a nonce-based policy is the superior choice.

SPAs with dynamic script injection

Single Page Applications (SPAs) built with React, Vue, or Angular often rely on dynamic script injection for code splitting and lazy loading. When a user navigates to a new route, the application dynamically fetches the necessary JavaScript chunks. A strict allowlist requires you to know every possible endpoint your bundler might pull from, which can be brittle and error-prone. A nonce-based approach, especially when combined with modern CSP directives, allows your application to load its chunks dynamically without maintaining a brittle list of origins. The initial application bundle is authorized via a nonce, and it can then securely load subsequent chunks as needed.

SSR pages with inline <script> from a CMS

Server-Side Rendered (SSR) applications frequently inject inline scripts to hydrate state, pass configuration variables from the backend to the frontend, or render content from a headless CMS. While you could use CSP hash sources for static inline scripts, hashes break the moment the content changes. If your inline script contains dynamic user data or a timestamp, its hash will change on every request, making a hash-based CSP impossible to maintain. A nonce is perfect here because the server can simply attach the current request’s nonce to the dynamically generated inline script, authorizing it regardless of its dynamic content.

Tag managers and analytics that inject scripts

Marketing teams love Google Tag Manager, Segment, and other analytics tools. These tools work by injecting additional scripts into the DOM at runtime. An allowlist cannot easily authorize these dynamically injected scripts without opening up massive security holes, as you would essentially have to allowlist the entire internet to accommodate every possible third-party script a tag manager might load. A nonce allows you to authorize the initial tag manager script, which can then propagate trust to its dependencies, ensuring that your marketing tools continue to function without compromising your application’s security posture.

Generating a nonce safely

Per-request, per-response (never per-session)

The most critical rule of using a CSP nonce is that it must be unique for every single HTTP response. You cannot generate a nonce when a user logs in and store it in their session. If a nonce is reused across multiple page loads, an attacker who extracts the nonce from one page can use it to bypass the CSP on another page. It must be generated fresh on the server just before the HTML is sent to the browser. This ensures that even if an attacker discovers the nonce for one specific request, it is useless for any subsequent requests.

Crypto requirements (≥128 bits, base64-encoded, CSPRNG)

A nonce must be unguessable. According to the W3C CSP3 nonce-source spec section, a nonce must be generated using a cryptographically secure pseudorandom number generator (CSPRNG). It should contain at least 128 bits of entropy and must be base64-encoded. Do not use standard Math.random() or equivalent functions, as they are predictable and can be reverse-engineered by a sophisticated attacker. Using a proper CSPRNG guarantees that the nonce is truly random and secure.

Express, Next.js, and Node.js examples

Here is how you generate a secure nonce in a Node.js environment using the built-in crypto module. This example demonstrates an Express middleware that generates a nonce, attaches it to the response locals for use in templates, and sets the CSP header. This pattern can be easily adapted for Next.js, Astro, Spring, Django, or any other backend framework.

const crypto = require('crypto');
const express = require('express');
const app = express();

app.use((req, res, next) => {
// Generate 16 bytes (128 bits) of random data and base64 encode it
const nonce = crypto.randomBytes(16).toString('base64');

// Make the nonce available to your template engine
res.locals.nonce = nonce;

// Set the Content-Security-Policy header
res.setHeader(
  'Content-Security-Policy',
  `default-src 'self'; script-src 'self' 'nonce-${nonce}';`
);

next();
});

app.get('/', (req, res) => {
res.send(`
  <!DOCTYPE html>
  <html>
    <head>
      <title>CSP Nonce Example</title>
    </head>
    <body>
      <h1>Hello World</h1>
      <!-- This script will execute because the nonce matches -->
      <script nonce="${res.locals.nonce}">
        console.log('Trusted inline script running!');
      </script>
    </body>
  </html>
`);
});

app.listen(3000);

Wiring the nonce into your CSP and your HTML

For the browser to validate the script, the exact same base64 string must appear in two places: the HTTP response header and the HTML attribute. If these two values do not match perfectly, the browser will block the script execution.

1. The HTTP Header

In your Content-Security-Policy header, the nonce is specified within the script-src (or style-src) directive, prefixed with ‘nonce-’ and enclosed in single quotes. This tells the browser to expect a nonce attribute on trusted scripts.

Content-Security-Policy: script-src 'self' 'nonce-rAnd0mB4se64Str1ng=='

2. The HTML Tag

In your HTML document, you add the nonce attribute to the <script> tag. The value is just the base64 string, without the ‘nonce-’ prefix or quotes. See the MDN script-src nonce reference for more details on how the attribute is processed by the browser.

<script nonce="rAnd0mB4se64Str1ng==">
// Your trusted code here
window.__INITIAL_STATE__ = { user: 'guest', theme: 'dark' };
console.log('Application state initialized.');
</script>

When the browser parses the HTML, it checks if the value of the nonce attribute matches the value provided in the CSP header. If it matches, the script executes normally. If it is missing or incorrect, the browser blocks the script and triggers a CSP violation, protecting your users from potentially malicious code.

strict-dynamic + nonce: the combo that scales

While a nonce allows an initial inline script to run, what happens if that script needs to load other scripts? For example, a Google Tag Manager snippet is an inline script, but its entire purpose is to inject additional <script src=”…”> tags into the document. By default, a nonce only authorizes the exact tag it is attached to. It does not authorize scripts created dynamically via document.createElement(‘script’). This means that even if you authorize the initial tag manager script with a nonce, all the subsequent scripts it tries to load will be blocked by the CSP.

To solve this, modern CSP introduced the ‘strict-dynamic’ keyword. When you combine a nonce with ‘strict-dynamic’, you tell the browser: “Trust the script with this nonce, and also trust any scripts that this trusted script dynamically injects.” This eliminates the need for massive allowlists and makes deploying CSP on complex sites feasible. The initial trusted script acts as a root of trust, and that trust is propagated to its dynamically loaded dependencies. Learn more about how to implement this powerful combination in our comprehensive guide to strict-dynamic.

Nonce mistakes to avoid

Implementing nonces incorrectly can either break your site or completely invalidate the security benefits of your Content Security Policy. Before enforcing your policy, you should always test it using a CSP evaluator to catch these common pitfalls and ensure your implementation is secure and effective.

Reusing the same nonce across responses

As mentioned earlier, a nonce is a number used once. If you generate a nonce at application startup and use it for every request, or if you store it in a user session and reuse it across multiple page loads, an attacker can easily discover the nonce and use it to bypass your policy. Always generate a fresh nonce for every single HTTP response to maintain the integrity of your CSP.

Caching pages with nonces in CDN

Warning: The CDN Caching Trap

If you cache a full HTML document at the CDN level (e.g., Cloudflare, Fastly, CloudFront), the CDN will cache the HTTP response headers (including the CSP header with the nonce) and the HTML body (including the script tags with the matching nonce). Every subsequent visitor will receive the exact same nonce. This completely defeats the security of a nonce-based CSP, as the nonce is no longer unique per response.

The Fix: You must bypass caching for HTML documents that use nonces, or use Edge Compute (like Cloudflare Workers, Next.js Edge Middleware, or Fastly Compute) to generate and inject a fresh nonce at the edge for every request, before serving the cached HTML. This ensures that every user receives a unique, secure nonce even when the underlying HTML is cached.

Forgetting style-src nonces

While nonces are most commonly discussed in the context of script-src, they are equally important for style-src. Many modern frontend frameworks and CSS-in-JS libraries (like Styled Components or Emotion) inject inline <style> tags into the document head to manage component styles dynamically. If you want to secure your styles without falling back to the dangerous ‘unsafe-inline’ directive, you must generate a nonce and apply it to your style-src directive and the corresponding style tags. This ensures that only your application’s legitimate styles are applied, preventing attackers from injecting malicious CSS that could alter the appearance of your site or exfiltrate sensitive data.

Frequently asked questions

Here are some of the most common questions developers have when implementing nonce-based Content Security Policies.

What is a CSP nonce?
A CSP nonce is a random, single-use token generated by the server for each HTTP response. It is included in the Content-Security-Policy header and added as an attribute to trusted script or style tags. The browser only executes inline scripts or styles that have a nonce matching the one in the header.
How do I generate a nonce for CSP?
You generate a CSP nonce using a cryptographically secure pseudorandom number generator (CSPRNG) on the server. The nonce must be at least 128 bits long and base64-encoded. It must be generated fresh for every single HTTP response and never reused across requests or sessions.
Can I cache pages that have a CSP nonce?
Generally, no. Because a nonce must be unique per response, caching a full HTML page with a nonce at the CDN or edge level will cause the same nonce to be served to multiple users, defeating its security purpose. You must either bypass caching for HTML documents, use edge compute (like Cloudflare Workers or edge middleware) to inject a fresh nonce, or rely on other CSP mechanisms for static pages.
Do I need a nonce for style-src too?
Yes, if you want to allow inline styles without using ‘unsafe-inline’. You can apply a nonce to <style> tags just like <script> tags, and include the nonce in your style-src directive. This is especially useful for CSS-in-JS libraries that inject styles dynamically.
What’s better: nonce, hash, or strict-dynamic?
They serve different purposes. Hashes are best for static inline scripts that never change. Nonces are ideal for dynamic inline scripts. strict-dynamic is used in combination with a nonce or hash to allow trusted scripts to load additional dependencies dynamically, which is essential for modern web applications and tag managers.

Conclusion

A nonce-based Content Security Policy is the most robust way to secure modern, dynamic web applications against Cross-Site Scripting. By generating a fresh, cryptographically secure token for every response, you can safely execute trusted inline scripts and dynamic loaders without maintaining fragile domain allowlists. This approach provides a strong security posture that scales with your application’s complexity.

However, rolling out a nonce-based policy requires careful implementation, especially regarding caching and dynamic script propagation. Before you enforce your policy and potentially break your production application, you need visibility into what your policy will block. Monitoring your policy in report-only mode is crucial for identifying missing nonces, caching issues, or unexpected third-party script behavior.

Validate your nonce-based CSP in production

Start free and watch what your nonce-based policy blocks before you enforce it. CSPify collects your violation reports, groups the noise, and helps you identify missing nonces or caching issues instantly.

Recommended next step

Validate your nonce-based CSP in production

Start free and watch what your nonce-based policy blocks before you enforce it.