HTTP Security Headers: The Complete Guide
Your website might have HTTPS, but that’s just the start. HTTP security headers tell browsers how to handle your content securely—blocking attacks that encryption alone can’t prevent.
These headers cost nothing to implement but protect against real threats: cross-site scripting (XSS), clickjacking, MIME-type sniffing, and more.
Why Security Headers Matter
Without security headers, browsers make permissive assumptions. They’ll:
- Execute inline scripts (XSS vector)
- Embed your pages in iframes (clickjacking)
- Guess content types (MIME sniffing attacks)
- Allow insecure upgrades (protocol downgrade attacks)
Security headers change these defaults to safer options.
Essential Security Headers
Content-Security-Policy (CSP)
The most powerful security header. CSP controls which resources can load on your page.
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:;
This policy:
default-src 'self'- Only load resources from your domainscript-src- Allow scripts from your domain and inline scriptsstyle-src- Allow styles from your domain and inline stylesimg-src- Allow images from your domain, data URIs, and any HTTPS source
CSP Directives
| Directive | Controls |
|---|---|
default-src | Fallback for all resource types |
script-src | JavaScript sources |
style-src | CSS sources |
img-src | Image sources |
font-src | Web font sources |
connect-src | XHR, WebSocket, fetch destinations |
frame-src | iframe sources |
frame-ancestors | Who can embed your page |
Common CSP Values
'self'- Same origin only'none'- Block all'unsafe-inline'- Allow inline scripts/styles (weakens protection)'unsafe-eval'- Allow eval() (avoid if possible)https:- Any HTTPS sourcedata:- Data URIsnonce-xyz- Specific inline scripts with matching nonce
X-Frame-Options
Prevents clickjacking by controlling iframe embedding:
X-Frame-Options: DENY
Options:
DENY- Never allow framingSAMEORIGIN- Only allow same-origin framingALLOW-FROM uri- Specific origin (deprecated, use CSP instead)
CSP’s frame-ancestors directive is more flexible, but X-Frame-Options provides fallback for older browsers.
X-Content-Type-Options
Prevents MIME type sniffing:
X-Content-Type-Options: nosniff
Without this, browsers might interpret a text file as JavaScript if an attacker can inject content. Always use nosniff.
Strict-Transport-Security (HSTS)
Forces HTTPS connections:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
max-age- How long to remember (seconds)includeSubDomains- Apply to all subdomainspreload- Submit to browser preload lists (permanent!)
Warning: Only enable preload if you’re certain your entire domain can be HTTPS-only forever.
Referrer-Policy
Controls how much referrer information to send:
Referrer-Policy: strict-origin-when-cross-origin
Common values:
no-referrer- Never send referrersame-origin- Only for same-origin requestsstrict-origin-when-cross-origin- Full path for same-origin, origin only for cross-origin HTTPS, nothing for HTTPorigin- Only send the origin (domain), not the full path
Permissions-Policy (formerly Feature-Policy)
Controls which browser features your page can use:
Permissions-Policy: camera=(), microphone=(), geolocation=()
This blocks camera, microphone, and geolocation—even if your JavaScript or embedded content tries to request them.
Recommended Configuration
Here’s a solid baseline for most websites:
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-ancestors 'none';
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Strict-Transport-Security: max-age=31536000; includeSubDomains
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
Adjust based on your needs:
- Using Google Analytics? Add it to
connect-srcandscript-src - Embedding YouTube? Add
frame-src https://www.youtube.com - Using Google Fonts? Add to
font-srcandstyle-src
Implementation by Server
Nginx
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self';" always;
Apache
Header always set X-Frame-Options "DENY"
Header always set X-Content-Type-Options "nosniff"
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Content-Security-Policy "default-src 'self';"
Vercel (vercel.json)
{
"headers": [
{
"source": "/(.*)",
"headers": [
{ "key": "X-Frame-Options", "value": "DENY" },
{ "key": "X-Content-Type-Options", "value": "nosniff" },
{ "key": "Strict-Transport-Security", "value": "max-age=31536000; includeSubDomains" }
]
}
]
}
Netlify (_headers file)
/*
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Strict-Transport-Security: max-age=31536000; includeSubDomains
Referrer-Policy: strict-origin-when-cross-origin
Testing Your Headers
Before deploying, test your configuration. Common issues:
- CSP too restrictive: Blocks legitimate resources, breaking functionality
- CSP too permissive:
'unsafe-inline'and'unsafe-eval'weaken protection - Missing headers: Easy to forget one
- Conflicting headers: Multiple definitions can cause unexpected behavior
Our Security Headers Checker analyzes any URL and grades your implementation:
- Checks for all recommended headers
- Validates CSP syntax and security
- Identifies missing headers
- Explains each header’s purpose
It’s free and provides specific recommendations for improvement.
Debugging CSP Issues
CSP is the most complex header and the most likely to cause problems.
Report-Only Mode
Test without breaking your site:
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report
This logs violations without blocking resources. Review the reports, then switch to enforcing mode.
Browser Console
CSP violations appear in the browser console:
Refused to load the script 'https://example.com/script.js'
because it violates the following Content Security Policy directive...
Add the blocked source to your policy (if legitimate) or investigate why it’s loading.
Security Headers and Third-Party Scripts
Modern websites often include third-party scripts: analytics, chat widgets, payment processors, ads. Each requires CSP exceptions.
The tradeoff:
- Strict CSP: Maximum security, but may break third-party integrations
- Permissive CSP: Works with everything, but less protection
Common approach:
- Start with a strict policy
- Add specific exceptions for each third-party
- Avoid wildcards when possible
script-src 'self' https://www.google-analytics.com https://js.stripe.com;
What About Legacy Headers?
Some headers are deprecated or unnecessary:
- X-XSS-Protection: Deprecated, can be removed. CSP is the modern solution.
- X-Powered-By: Remove it—it just reveals your tech stack to attackers
- Expect-CT: Deprecated since Certificate Transparency is now required
Take Action
- Run your site through our Security Headers Checker
- Implement the missing headers (start with X-Frame-Options and X-Content-Type-Options—they’re low risk)
- Add HSTS once you’re confident your site works over HTTPS
- Implement CSP in report-only mode first
If you need help with security configuration or a comprehensive security audit, get in touch.