Security Headers Configuration Guide: CSP / HSTS / X-Frame-Options
HTTP security headers are the first line of browser-side defense. Proper configuration effectively prevents XSS, clickjacking, MIME sniffing, and other attacks.
1. Core Security Headers
1.1 Content-Security-Policy (CSP)
The strongest defense against XSS and injection attacks.
add_header Content-Security-Policy "
default-src 'self';
script-src 'self' 'unsafe-inline' https://trusted-cdn.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-src 'self';
object-src 'none';
base-uri 'self';
form-action 'self';
" always;
CSP Deployment Strategy:
Phase 1: Report-only mode (observe violations)
Phase 2: Relaxed policy (gradually tighten)
Phase 3: Strict policy (remove unsafe-inline)
1.2 Strict-Transport-Security (HSTS)
Tells browsers to always use HTTPS.
# Production (1 year)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
1.3 X-Frame-Options
Prevents clickjacking attacks.
add_header X-Frame-Options "DENY" always;
# or
add_header X-Frame-Options "SAMEORIGIN" always;
1.4 X-Content-Type-Options
Prevents MIME sniffing.
add_header X-Content-Type-Options "nosniff" always;
1.5 Referrer-Policy
Controls referrer information.
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
1.6 Permissions-Policy
Controls browser API permissions.
add_header Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=()" always;
2. Complete Security Headers Configuration
Nginx
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=()" always;
Two details bite people in practice. First, Nginx's add_header only applies to "success-class" responses (200/201/204/206/301/302/304/307/308) unless you add always — and error pages are exactly what attackers probe most, so they often end up missing the headers; Apache's Header always set works the same way. Second, people configure the main domain but forget subdomains, leaving HSTS coverage incomplete.
If you sit behind a CDN or cloud load balancer (e.g. Cloudflare), set security headers at both origin and edge: the origin protects direct connections, and an edge "modify response headers" rule gives you one place to change everything globally.
3. Validation Tools
- Security Headers (securityheaders.com)
- Mozilla Observatory (observatory.mozilla.org)
- CSP Evaluator (csp-evaluator.withgoogle.com)
A Real Incident
In 2024 an e-commerce site's avatar upload endpoint lacked X-Content-Type-Options: nosniff. An attacker uploaded HTML disguised as an image, embedded a phishing script, and visitors' browsers rendered it as a page via MIME sniffing — the session cookie nearly got stolen. The fix was a single line of config. Similarly, a site missing X-Frame-Options can be wrapped in an iframe by a third party, tricking users into clicking "authorize" without realizing it: classic clickjacking. Security headers cannot stop every attack, but they close off a large class of common browser-side ones.
4. Common Issues
- CSP too strict blocking third-party scripts
- HSTS causing local dev environment issues
Using CSP Reports
Before going strict, run Content-Security-Policy-Report-Only with a report endpoint (report-uri or report-to) for a week or two, collect real violations, relax or fix them one by one, then switch to enforcing mode. Turning on a strict policy and rolling back after the page goes blank is the classic failure mode.
Do You Still Need X-XSS-Protection
X-XSS-Protection: 1; mode=block is a legacy header from the old browser era and is now deprecated in modern browsers (Chrome even ships X-XSS-Protection: 0 to turn it off); real XSS defense belongs to CSP. Keep the line for compatibility, but do not rely on it.
nonce vs hash for inline scripts
Inline scripts are awkward: 'unsafe-inline' opens everything up and weakens CSP. The finer-grained approach is a one-time nonce or an SRI hash. Give <script nonce="abc123"> a script-src 'self' 'nonce-abc123' and rotate the nonce on every response, so an attacker cannot predict or replay it.
5. Summary
HTTP security headers are among the most cost-effective security measures. Start with X-Frame-Options, X-Content-Type-Options, and Referrer-Policy, then gradually deploy CSP and HSTS.
Reference: OWASP Secure Headers cheat sheet https://owasp.org/www-project-secure-headers/ ; MDN on CSP https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP ; HSTS preload https://hstspreload.org/