Why Security Headers Matter
HTTP security headers are your first line of defense against many common web attacks. These response headers instruct browsers how to behave when handling your site's content, providing protection against cross-site scripting (XSS), clickjacking, code injection, and information leakage.
Despite their importance, research shows that over 95% of websites fail to implement even basic security headers correctly. This guide provides a comprehensive overview of essential security headers and how to implement them effectively.
Content Security Policy (CSP)
Content Security Policy is the most powerful—and most complex—security header available. CSP defines approved sources of content that browsers can load on your pages, preventing attackers from injecting malicious scripts even if they find an XSS vulnerability.
CSP Directives
A comprehensive CSP policy includes multiple directives:
Content-Security-Policy: default-src 'self';
script-src 'self' https://cdn.example.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';Key CSP directives explained:
- default-src: Fallback for other directives. Set to 'self' to only allow resources from your domain
- script-src: Controls which scripts can execute. Avoid 'unsafe-inline' and 'unsafe-eval' when possible
- style-src: Controls stylesheet sources. Many sites need 'unsafe-inline' for third-party widgets
- img-src: Controls image sources. Use https: to allow any HTTPS image
- connect-src: Controls fetch(), XMLHttpRequest, WebSocket, and EventSource connections
- frame-ancestors: Replaces X-Frame-Options, controls who can embed your page in frames
- base-uri: Restricts the URLs that can be used in a page's <base> element
- form-action: Restricts the URLs that forms can submit to
Implementing CSP Without Breaking Your Site
CSP implementation should be gradual:
- Report-Only Mode: Deploy CSP in report-only mode first:
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report - Collect Violations: Monitor violation reports for 1-2 weeks to identify legitimate resources being blocked
- Refine Policy: Adjust your policy to allow necessary resources while maintaining security
- Enforce Policy: Switch from Report-Only to enforcement mode
- Continuous Monitoring: Keep monitoring reports to catch new violations
CSP Level 3 and Nonces
Modern CSP implementations use nonces (cryptographic tokens) instead of 'unsafe-inline':
Content-Security-Policy: script-src 'nonce-{RANDOM}';
<script nonce="{RANDOM}">
// Your inline script
</script>The nonce must be randomly generated for each page load and match between the header and script tag. This allows specific inline scripts while blocking injected ones.
HTTP Strict Transport Security (HSTS)
HSTS forces browsers to only connect to your site over HTTPS, preventing protocol downgrade attacks and cookie hijacking.
Strict-Transport-Security: max-age=31536000; includeSubDomains; preloadParameters explained:
- max-age: Duration (seconds) the browser should remember to only use HTTPS. 31536000 = 1 year
- includeSubDomains: Applies policy to all subdomains. Only use if all subdomains support HTTPS
- preload: Allows inclusion in browser HSTS preload lists for first-visit protection
HSTS Preload List
The HSTS preload list is a hardcoded list of domains built into browsers. To be included:
- Serve a valid certificate
- Redirect all HTTP traffic to HTTPS
- Serve HSTS header on all HTTPS responses with max-age of at least 31536000 seconds
- Include the 'preload' directive
- Submit your domain to hstspreload.org
Warning: Preload list inclusion is difficult to reverse. Ensure all subdomains support HTTPS before submission.
X-Content-Type-Options
Prevents MIME type sniffing, where browsers try to detect content type by analyzing file contents rather than trusting the Content-Type header.
X-Content-Type-Options: nosniffWithout this header, an attacker could upload a malicious file disguised as an image, and the browser might execute it as JavaScript. Always include this header.
X-Frame-Options and Frame-Ancestors
Protects against clickjacking attacks where your site is embedded in an invisible iframe to trick users into clicking malicious elements.
X-Frame-Options: DENYOptions:
- DENY: Page cannot be displayed in a frame under any circumstances
- SAMEORIGIN: Page can be framed only by pages on the same origin
Modern sites should use CSP's frame-ancestors directive instead, which provides more granular control:
Content-Security-Policy: frame-ancestors 'none';Referrer-Policy
Controls how much referrer information is sent when navigating away from your site.
Referrer-Policy: strict-origin-when-cross-originPolicy options (from least to most restrictive):
- unsafe-url: Full URL sent in all cases (not recommended)
- origin-when-cross-origin: Full URL for same-origin, origin only for cross-origin
- strict-origin-when-cross-origin: Full URL for same-origin HTTPS, origin only for cross-origin HTTPS, nothing for HTTPS→HTTP (recommended)
- no-referrer: Never send referrer information (breaks some analytics)
Permissions-Policy (formerly Feature-Policy)
Controls which browser features and APIs can be used by your site and embedded content.
Permissions-Policy: geolocation=(), microphone=(), camera=()Common features to restrict:
- geolocation: GPS location access
- microphone: Microphone access
- camera: Camera access
- payment: Payment Request API
- usb: WebUSB API
- magnetometer: Magnetometer sensor
Syntax allows allowlists: Permissions-Policy: geolocation=(self "https://maps.example.com")
Cross-Origin-Embedder-Policy and Cross-Origin-Opener-Policy
These headers enable 'cross-origin isolation', required for powerful features like SharedArrayBuffer:
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-originWarning: These headers can break embedded content and pop-ups. Test thoroughly before deployment.
Implementation Across Platforms
Apache (.htaccess)
<IfModule mod_headers.c>
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'"
</IfModule>Nginx
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'" always;WordPress (functions.php)
function add_security_headers() {
header('X-Frame-Options: SAMEORIGIN');
header('X-Content-Type-Options: nosniff');
header('Referrer-Policy: strict-origin-when-cross-origin');
header('Permissions-Policy: geolocation=(), microphone=(), camera=()');
}
add_action('send_headers', 'add_security_headers');Testing Your Security Headers
Use these tools to validate your implementation:
- securityheaders.com: Comprehensive header analysis with letter grades
- Mozilla Observatory: Detailed security recommendations
- Browser DevTools: Network tab shows all response headers
- csp-evaluator.withgoogle.com: Specifically tests CSP policies
Common Pitfalls and Solutions
CSP Breaking Third-Party Widgets
Many third-party services (analytics, ads, chat widgets) require 'unsafe-inline' or 'unsafe-eval'. Solutions:
- Use nonces for your own inline scripts
- Load third-party scripts from their CDN domains (add to script-src)
- Consider switching to privacy-respecting alternatives that support strict CSP
HSTS Locking Out Users
If you deploy HSTS then lose your SSL certificate, users cannot access your site. Always:
- Test with low max-age values first (300 seconds)
- Ensure your certificate renewal process is automated
- Have monitoring in place to alert on certificate expiration
Breaking Mobile Apps
Mobile apps that embed webviews may be affected by strict security headers. Test all native apps after header deployment.
Monitoring and Maintenance
Security headers require ongoing maintenance:
- Monitor CSP violation reports weekly
- Review headers after adding new third-party services
- Test headers after platform upgrades
- Keep policies updated as browser features evolve
- Audit header effectiveness quarterly
Conclusion
Security headers are essential for modern web security. While implementation can be challenging, particularly for CSP, the protection they provide is worth the effort. Start with basic headers (X-Frame-Options, X-Content-Type-Options, HSTS) and gradually implement more advanced policies like CSP and Permissions-Policy.
Remember: security headers are defense in depth—they protect against exploitation even when vulnerabilities exist in your code. They should be part of every website's security strategy.