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

  1. Security Headers (securityheaders.com)
  2. Mozilla Observatory (observatory.mozilla.org)
  3. 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/