Security headers are HTTP response headers that harden browsers against common attacks like XSS, clickjacking, downgrade attacks, and data leakage. They are quick wins that reduce risk without changing application code in many cases.
Table of Contents
- Quick checklist
- Content-Security-Policy (CSP)
- Strict-Transport-Security (HSTS)
- X-Frame-Options (and frame-ancestors)
- Other high-value security headers
- Common mistakes and how to avoid them
- Security headers checker: what it does and what to look for
- Practical fixes (by platform)
- FAQs
Quick checklist
- Content-Security-Policy: present, specific, minimal allowlists, no unnecessary unsafe-inline or unsafe-eval
- Strict-Transport-Security: enabled on HTTPS, long max-age, includeSubDomains if safe, preload if ready
- X-Frame-Options: set to DENY or SAMEORIGIN (or use CSP frame-ancestors)
- Referrer-Policy: limits referrer leakage
- Permissions-Policy: disables unused browser features
- Cross-Origin-Opener-Policy / Cross-Origin-Resource-Policy: reduces cross-origin data exposure
- Cache-Control (for sensitive pages): prevents storing private content
Content-Security-Policy (CSP)
What it protects against: CSP reduces the impact of cross-site scripting (XSS), malicious third-party scripts, and unsafe content loading by restricting where resources can be loaded from.
How it works: You define a policy that browsers enforce. The policy describes allowed sources for scripts, styles, images, fonts, frames, and more.
Good starting policy (example):
- Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; upgrade-insecure-requests
Key directives to understand:
- default-src: fallback for other resource types
- script-src: controls JavaScript sources (most important for XSS)
- style-src: controls CSS sources
- img-src, font-src, connect-src: images, fonts, and XHR/WebSocket destinations
- object-src: set to 'none' to block plugins
- base-uri: prevents hostile base URL injection
- frame-ancestors: clickjacking defense (preferred over X-Frame-Options when available)
- upgrade-insecure-requests: upgrades HTTP subresources to HTTPS
Common CSP pitfalls:
- unsafe-inline in script-src: weakens XSS defenses. Prefer nonces or hashes.
- unsafe-eval: enables dangerous JS evaluation patterns.
- Overly broad allowlists like * or entire CDNs without need.
- Breaking the site by enforcing too early. Use Content-Security-Policy-Report-Only first to collect violations.
Report-only mode (recommended rollout):
- Content-Security-Policy-Report-Only: default-src 'self'; ...; report-to csp-endpoint
Strict-Transport-Security (HSTS)
What it protects against: HSTS blocks protocol downgrade attacks and cookie hijacking by forcing browsers to use HTTPS for your domain after the first secure visit.
How it works: When a browser sees HSTS, it caches the rule and refuses to use HTTP for that domain until the max-age expires.
Recommended header (example):
- Strict-Transport-Security: max-age=31536000; includeSubDomains
Preload option (only if you meet requirements):
- Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
HSTS deployment warnings:
- Do not enable includeSubDomains unless every subdomain is on HTTPS.
- Be careful with long max-age on environments that are not ready, like legacy subdomains or staging.
- Preload is hard to roll back. Only do it when you are confident.
X-Frame-Options (and frame-ancestors)
What it protects against: clickjacking, where an attacker frames your site and tricks users into clicking UI elements.
X-Frame-Options values:
- DENY: no framing allowed anywhere
- SAMEORIGIN: allow framing only on the same origin
Recommended modern approach: Prefer CSP frame-ancestors because it is more flexible and widely supported in modern browsers.
Examples:
- X-Frame-Options: DENY
- Content-Security-Policy: frame-ancestors 'none'
- Content-Security-Policy: frame-ancestors 'self' https://trusted.example
Note: If you set both X-Frame-Options and CSP frame-ancestors, ensure they do not conflict.
Other high-value security headers
- Referrer-Policy: strict-origin-when-cross-origin (good default)
- Permissions-Policy: disable unused features (example: geolocation=(), microphone=(), camera=())
- X-Content-Type-Options: nosniff to prevent MIME sniffing
- Cross-Origin-Opener-Policy (COOP): same-origin to isolate browsing context
- Cross-Origin-Resource-Policy (CORP): same-origin or same-site depending on needs
- Cross-Origin-Embedder-Policy (COEP): powerful isolation, but can break third-party embeds
- Cache-Control (for sensitive pages): no-store where appropriate
About X-XSS-Protection: This is legacy and not recommended as a security control for modern browsers.
Common mistakes and how to avoid them
- CSP is present but ineffective: policies that allow everything (wildcards, unsafe-inline) do not help much.
- HSTS on some pages only: HSTS must be sent consistently on HTTPS responses for the host.
- Conflicting clickjacking controls: X-Frame-Options says DENY but CSP frame-ancestors allows framing, or vice versa.
- Setting headers on the app but missing the CDN or reverse proxy: ensure the edge layer is not stripping or overriding headers.
- Applying strict policies to every path: some pages need different rules (for example, an embedded widget). Use per-route config where needed.
Security headers checker: what it does and what to look for
A security headers checker fetches your site and analyzes response headers to identify missing protections, weak configurations, and inconsistencies across endpoints.
What a good checker should verify:
- Presence and correctness of core headers: CSP, HSTS, X-Frame-Options (or frame-ancestors), Referrer-Policy, Permissions-Policy, X-Content-Type-Options
- Strength checks, not just presence (for example, CSP without unsafe-inline, HSTS max-age length)
- Redirect chain behavior (HTTP to HTTPS, and whether headers appear on final response)
- Consistency across key paths (/, login, admin, API endpoints)
- Edge layer overrides (CDN, WAF, load balancer) that add or remove headers
What you should do with results:
- Treat missing as priority fixes, especially HSTS, clickjacking protections, and nosniff.
- Treat weak as hardening tasks, especially CSP and Permissions-Policy.
- Re-test multiple endpoints after changes to confirm the header is actually served.
Practical fixes (by platform)
- Nginx: add headers at the server or location block, and use the always flag where appropriate so they apply to error responses too.
- Apache: set headers in VirtualHost or .htaccess (prefer server config for consistency).
- CDN / Reverse proxy: apply at the edge for uniform coverage, but ensure application-level headers do not conflict.
- Framework middleware: good for route-specific CSP, but verify the final response after proxies.
Rule of thumb: Set broad, safe defaults at the edge, then tighten CSP per app route if needed.
FAQs
Should I use both X-Frame-Options and CSP frame-ancestors?
Yes, you can, but keep them aligned. If you already use frame-ancestors, X-Frame-Options becomes a compatibility layer for older clients.
Will CSP break my site?
It can if deployed in enforce mode without testing. Start with Report-Only, fix violations, then enforce.
Is HSTS safe to enable immediately?
Only if all content and subdomains you plan to include are available over HTTPS. Start without includeSubDomains if you are unsure.
Do security headers replace secure coding?
No. They reduce browser-side attack surface and impact, but they do not eliminate server-side vulnerabilities.