Content Security Policy is an HTTP header that tells browsers which sources of content are trusted. A well-configured CSP prevents Cross-Site Scripting (XSS), clickjacking, and other code injection attacks by restricting where scripts, styles, and other resources can be loaded from.
How CSP Works
# The browser enforces CSP rules:
# 1. Server sends Content-Security-Policy header
# 2. Browser parses the policy directives
# 3. Any resource load that violates the policy is blocked
# 4. Violations are reported (if report-uri is configured)Building a CSP Policy
# Start restrictive and loosen as needed
# Nginx example:
add_header Content-Security-Policy "
default-src 'self';
script-src 'self' https://cdn.jsdelivr.net;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
font-src 'self' https://fonts.gstatic.com;
img-src 'self' data: https:;
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
" always;CSP Directives Reference
- default-src — Fallback for all resource types
- script-src — JavaScript sources
- style-src — CSS sources
- img-src — Image sources
- font-src — Font sources
- connect-src — AJAX, WebSocket, fetch destinations
- media-src — Audio and video sources
- frame-src — iframe sources
- frame-ancestors — Who can iframe your page (replaces X-Frame-Options)
- base-uri — Restricts the base element
- form-action — Where forms can submit to
Testing with Report-Only Mode
# Use Content-Security-Policy-Report-Only to test without blocking
add_header Content-Security-Policy-Report-Only "
default-src 'self';
script-src 'self';
report-uri /csp-report;
" always;
# This logs violations without breaking your site
# Review reports and adjust the policy before enforcingCommon CSP Patterns
# Strict CSP for a static site
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self'; font-src 'self'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'
# CSP for a site using Google Analytics and Fonts
Content-Security-Policy: default-src 'self'; script-src 'self' https://www.googletagmanager.com https://www.google-analytics.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src https://fonts.gstatic.com; img-src 'self' https://www.google-analytics.com; connect-src 'self' https://www.google-analytics.com
# CSP with nonce-based script execution (strongest)
# Generate a unique nonce per request
Content-Security-Policy: script-src 'nonce-abc123'; style-src 'nonce-abc123'
# Then in HTML: <script nonce="abc123">...</script>Other Security Headers
# Complete security headers in Nginx:
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header X-XSS-Protection "0" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;Testing Your Headers
# Check headers with curl
curl -I https://yourdomain.com
# Online tools:
# https://securityheaders.com
# https://observatory.mozilla.org
# Aim for an A+ rating on both