The Problem

Content Security Policy (CSP) headers tell the browser which sources are allowed to execute scripts, load styles, and make requests. A strict CSP blocks inline scripts (<script> tags), inline styles (style="..."), and eval() calls.

Symptoms

  • Console error: "Refused to execute inline script because it violates CSP"
  • JavaScript code does not run
  • Analytics, chat widgets, or third-party scripts fail to load
  • Page renders without interactive features
  • Works locally but not in production

Real Error Message

bash
Refused to execute inline script because it violates the following
Content Security Policy directive: "script-src 'self'". Either the
'unsafe-inline' keyword, a hash ('sha256-...'), or a nonce
('nonce-...') is required to enable inline execution.

Common Causes

Cause 1: Inline Script Tags

html
<!-- BLOCKED by CSP -->
<script>
  window.dataLayer = window.dataLayer || [];
  gtag('config', 'GA-XXXX');
</script>

Cause 2: Inline Event Handlers

html
<!-- BLOCKED by CSP -->
<button onclick="handleSubmit()">Submit</button>

Cause 3: Third-Party Scripts

html
<!-- BLOCKED by CSP if not in script-src -->
<script src="https://www.googletagmanager.com/gtag/js?id=GA-XXXX"></script>

How to Fix It

Fix 1: Use Nonces for Inline Scripts

html
<!-- Generate nonce server-side -->
<script nonce="abc123-random">
  window.dataLayer = window.dataLayer || [];
</script>
bash
Content-Security-Policy: script-src 'self' 'nonce-abc123-random' https://www.googletagmanager.com

Fix 2: Use Hashes for Static Inline Scripts

bash
# Generate SHA-256 hash of the inline script
echo -n "window.dataLayer = [];" | openssl dgst -sha256 -binary | openssl base64 -A
# Output: sha256-abc123...
bash
Content-Security-Policy: script-src 'self' 'sha256-abc123...'

Fix 3: Move Inline Scripts to External Files

javascript
// analytics.js
window.dataLayer = window.dataLayer || [];
gtag('config', 'GA-XXXX');
html
<script src="/analytics.js"></script>

Fix 4: Replace Inline Event Handlers

javascript
// Instead of onclick="handleSubmit()"
document.getElementById('submit-btn').addEventListener('click', handleSubmit);

Fix 5: Proper CSP Header Configuration

nginx
# nginx.conf
add_header Content-Security-Policy "
  default-src 'self';
  script-src 'self' https://www.googletagmanager.com https://cdn.example.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://api.example.com;
  frame-ancestors 'none';
" always;

Fix 6: Report-Only Mode for Testing

nginx
# Test CSP before enforcing
add_header Content-Security-Policy-Report-Only "
  default-src 'self';
  script-src 'self';
  report-uri /csp-report;
" always;