TL;DR
The #1 security headers best practice is to add defense-in-depth headers that protect against XSS, clickjacking, and protocol downgrade attacks. Start with X-Content-Type-Options, X-Frame-Options, and Strict-Transport-Security. Add Content-Security-Policy for best protection. Test with securityheaders.com.
"Security headers are your first line of defense. They cost nothing to implement but protect against entire classes of attacks."
Essential Security Headers
Every web application should include these headers:
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
Strict-Transport-Security: max-age=31536000; includeSubDomains
Header-by-Header Explanation
Strict-Transport-Security (HSTS) 2 min
Forces browsers to use HTTPS, preventing protocol downgrade attacks:
Strict-Transport-Security: max-age=31536000; includeSubDomains
// Options:
// max-age: Time in seconds to remember HTTPS-only (1 year recommended)
// includeSubDomains: Apply to all subdomains
// preload: Submit to browser preload list (permanent, use carefully)
HSTS is sticky. Once set, browsers will refuse HTTP connections for the max-age period. Start with a short max-age (3600) to test, then increase to 31536000 (1 year).
Content-Security-Policy (CSP) 5 min
The most powerful header for preventing XSS:
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' https://fonts.gstatic.com; connect-src 'self' https://api.yourdomain.com; frame-ancestors 'none'
| Directive | Controls | Recommended Value |
|---|---|---|
| default-src | Fallback for all resource types | 'self' |
| script-src | JavaScript sources | 'self' (avoid 'unsafe-inline') |
| style-src | CSS sources | 'self' 'unsafe-inline' |
| img-src | Image sources | 'self' data: https: |
| connect-src | Fetch, XHR, WebSocket | 'self' your-api |
| frame-ancestors | Who can embed you | 'none' or 'self' |
X-Frame-Options 1 min
Prevents clickjacking by controlling if your site can be embedded:
X-Frame-Options: DENY // Cannot be embedded anywhere
X-Frame-Options: SAMEORIGIN // Only same origin can embed
X-Frame-Options: ALLOW-FROM uri // Deprecated, use CSP instead
X-Content-Type-Options 1 min
Prevents MIME type sniffing attacks:
X-Content-Type-Options: nosniff
// Without this, browsers might execute a file as JavaScript
// even if served with a different Content-Type
Referrer-Policy 1 min
Controls what referrer information is sent with requests:
Referrer-Policy: no-referrer // Never send referrer
Referrer-Policy: strict-origin-when-cross-origin // Recommended
Referrer-Policy: same-origin // Only for same-origin
Permissions-Policy 1 min
Controls which browser features your site can use:
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()
// Disable features you don't use to reduce attack surface
// () = disabled, (self) = this origin only, (*) = any origin
Framework Configuration
Next.js 2 min
module.exports = {
async headers() {
return [
{
source: '/(.*)',
headers: [
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'X-Frame-Options', value: 'DENY' },
{ key: 'X-XSS-Protection', value: '1; mode=block' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
{ key: 'Permissions-Policy', value: 'camera=(), microphone=()' },
],
},
];
},
};
Express 2 min
import helmet from 'helmet';
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
},
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
},
}));
Vercel/Netlify 1 min
{
"headers": [
{
"source": "/(.*)",
"headers": [
{ "key": "X-Content-Type-Options", "value": "nosniff" },
{ "key": "X-Frame-Options", "value": "DENY" },
{ "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" }
]
}
]
}
Testing Your Headers
Use these tools to verify your configuration:
- securityheaders.com: Comprehensive header analysis
- Mozilla Observatory: Full security audit
- Browser DevTools: Network tab shows response headers
- curl -I: Quick header check from terminal
Learn More: For comprehensive documentation on security headers, see MDN Web Docs: HTTP Security Headers and OWASP Secure Headers Project.
Do I need both X-Frame-Options and frame-ancestors?
For maximum compatibility, use both. CSP frame-ancestors is more flexible and overrides X-Frame-Options in modern browsers, but X-Frame-Options supports older browsers.
Why does CSP break my site?
CSP blocks resources that do not match your policy. Start with report-only mode (Content-Security-Policy-Report-Only) to find violations without breaking anything, then fix and switch to enforcing mode.
Should I use X-XSS-Protection?
Modern browsers have deprecated it, and it can introduce vulnerabilities in older browsers. Include it for legacy support but rely on CSP for real protection.
Check Your Security Headers
Scan your site for missing or misconfigured security headers.
Start Free Scan