Security Headers Every Website Should Send
You've locked down your server, patched your framework, and trained your team on secure coding. Yet your site remains vulnerable if it doesn't send the right HTTP security headers. These response headers tell browsers how to behave when handling your content, and missing or misconfigured headers leave the door open to cross-site scripting (XSS), clickjacking, protocol downgrade attacks, and data leakage.
In this guide, we'll cover the essential security headers every website should send, explain what each one does, and show you how to configure them correctly.
Why Security Headers Matter
Security headers are a first line of defense because they enforce browser-level policies that you control. They don't replace input validation or secure authentication, but they significantly reduce the attack surface. For example, a strict Content-Security-Policy can stop an injected script from executing, even if an attacker finds an XSS flaw.
Major browsers support these headers consistently, and adding them is usually a few lines of configuration. There's little reason not to use them.
The Essential Security Headers
Below are the headers that every production website should send. We'll cover what they do, recommended values, and common pitfalls.
1. Content-Security-Policy (CSP)
CSP is the most powerful header for mitigating XSS and data injection. It restricts which sources the browser can load scripts, styles, images, and other resources from. A good starting policy might look like:
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.cdn.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://images.example.com; object-src 'none'; base-uri 'self'; form-action 'self';Start with default-src 'self' and gradually add exceptions as needed. Avoid 'unsafe-inline' for scripts if possible; use nonces or hashes instead. Test thoroughly because a misconfigured CSP can break your site.
2. HTTP Strict Transport Security (HSTS)
HSTS forces browsers to use HTTPS for all future requests to your domain. It prevents protocol downgrade attacks and cookie hijacking. A typical header:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preloadThe max-age is in seconds (one year). includeSubDomains applies the policy to all subdomains. preload allows your domain to be added to browser preload lists, but only use it if you're absolutely sure all subdomains support HTTPS.
3. X-Frame-Options
This header prevents your site from being embedded in an iframe, which stops clickjacking attacks. Use:
X-Frame-Options: DENYOr SAMEORIGIN if you need to frame your own content. Modern browsers also support the frame-ancestors directive in CSP, which is more flexible. If you use CSP, you can omit X-Frame-Options, but including both provides better compatibility.
4. X-Content-Type-Options
This header stops browsers from MIME-sniffing a response away from the declared Content-Type. It's simple and effective:
X-Content-Type-Options: nosniffWithout it, a malicious file might be interpreted as executable script. Always set it.
5. Referrer-Policy
Referrer-Policy controls how much referrer information is sent with requests. A balanced default:
Referrer-Policy: strict-origin-when-cross-originThis sends the full URL for same-origin requests and only the origin for cross-origin requests. It reduces leakage of sensitive path information while preserving analytics.
6. Permissions-Policy
Formerly Feature-Policy, this header lets you enable or disable browser features like geolocation, camera, and microphone. Example:
Permissions-Policy: geolocation=(), camera=(), microphone=()Disabling unused features reduces the impact of compromised third-party scripts.
Comparison of Security Headers
| Header | Purpose | Recommended Value |
|---|---|---|
| Content-Security-Policy | Mitigate XSS and data injection | default-src 'self'; script-src 'self' ... |
| Strict-Transport-Security | Enforce HTTPS | max-age=31536000; includeSubDomains |
| X-Frame-Options | Prevent clickjacking | DENY or SAMEORIGIN |
| X-Content-Type-Options | Stop MIME sniffing | nosniff |
| Referrer-Policy | Control referrer leakage | strict-origin-when-cross-origin |
| Permissions-Policy | Restrict browser features | geolocation=(), camera=() |
How to Add Security Headers
The method depends on your web server or framework. Here are common approaches.
Nginx
Add headers in your server block:
add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://trusted.cdn.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://images.example.com; object-src 'none'; base-uri 'self'; form-action 'self';" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), camera=(), microphone=()" always;The always parameter ensures headers are sent even on error responses.
Apache
Enable mod_headers and add:
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' https://trusted.cdn.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://images.example.com; object-src 'none'; base-uri 'self'; form-action 'self';"
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Header always set X-Frame-Options "DENY"
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "geolocation=(), camera=(), microphone=()"Node.js (Express)
Use the helmet middleware, which sets many headers by default:
const helmet = require('helmet');
app.use(helmet());
// Customize CSP if needed
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "https://trusted.cdn.com"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https://images.example.com"],
objectSrc: ["'none'"],
baseUri: ["'self'"],
formAction: ["'self'"],
}
}));Helmet also sets other headers like X-Content-Type-Options and Referrer-Policy by default.
Testing Your Headers
After deployment, verify your headers using browser developer tools (Network tab) or online scanners like SecurityHeaders.com. Check that:
- All recommended headers are present.
- CSP doesn't block legitimate resources (check console for violations).
- HSTS is only enabled if HTTPS is fully working.
- Headers are sent on error pages as well.
Regularly review and update your policies as your site evolves.
FAQ
What is the most important security header?
Content-Security-Policy is often considered the most important because it directly mitigates XSS and data injection attacks, which are among the most common web vulnerabilities.
Can security headers replace other security measures?
No. Security headers are a defense-in-depth layer. You still need secure coding, input validation, authentication, and other best practices.
Will adding security headers break my website?
If misconfigured, especially CSP, they can block legitimate resources. Always test in a staging environment and monitor browser console for violations before deploying to production.
Ready to lock down your site? Start by adding the headers above, then test with your browser's developer tools. For a quick way to inspect and format JSON responses from your security scanner, try our JSON Formatter.