HTTP Security Headers: The Complete Checklist
Protect your website with the right HTTP security headers. HSTS, CSP, X-Frame-Options, and more — what they do and how to set them up.
Why Security Headers Matter
Security headers are instructions your server sends to the browser about how to handle your content. They prevent clickjacking, XSS attacks, data injection, and protocol downgrades — with zero impact on user experience. Your visitors never see them, but attackers do.
Most websites are missing critical security headers. A 2025 study found that only 20% of the top million websites had a Content-Security-Policy, and 35% were missing basic protections like X-Content-Type-Options. These are free, easy fixes.
The Essential Headers
1. Strict-Transport-Security (HSTS)
What it prevents: SSL stripping attacks, where an attacker downgrades your HTTPS connection to HTTP to intercept traffic.
Forces browsers to always use HTTPS for your domain. Once a browser sees this header, it will refuse to connect over HTTP — even if the user types http:// or clicks an HTTP link.
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
max-age=31536000— Remember for 1 year (31.5 million seconds)includeSubDomains— Apply to all subdomains (api.example.com, cdn.example.com, etc.)preload— Submit to browser preload lists for ultimate protection (browsers will enforce HTTPS before ever connecting)
Warning: Only add preload after you're certain HTTPS works perfectly on all subdomains. Getting into the preload list is easy; getting removed takes months.
2. Content-Security-Policy (CSP)
What it prevents: Cross-site scripting (XSS), data injection, and unauthorized resource loading.
Controls exactly which resources the browser can load on your pages — scripts, styles, images, fonts, frames, everything. This is the most powerful security header and the most complex to configure.
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' https://fonts.gstatic.com; frame-ancestors 'none'
Start with Report-Only mode to test without breaking anything:
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report
This logs violations without blocking them, so you can see what would break before enforcing.
CSP breakdown by directive:
| Directive | Controls | Example |
|-----------|----------|---------|
| default-src | Fallback for all resource types | 'self' |
| script-src | JavaScript sources | 'self' 'unsafe-inline' https://cdn.example.com |
| style-src | CSS sources | 'self' 'unsafe-inline' |
| img-src | Image sources | 'self' data: https: |
| font-src | Font file sources | 'self' https://fonts.gstatic.com |
| connect-src | XHR, fetch, WebSocket targets | 'self' https://api.example.com |
| frame-ancestors | Who can embed your site in iframes | 'none' or 'self' |
| upgrade-insecure-requests | Auto-upgrade HTTP to HTTPS | (no value needed) |
3. X-Content-Type-Options
What it prevents: MIME type confusion attacks, where a browser interprets a file as a different type than declared (e.g., treating a text file as executable JavaScript).
X-Content-Type-Options: nosniff
This header has one value. Always set it. There's no reason not to.
4. X-Frame-Options
What it prevents: Clickjacking — an attacker embeds your site in an invisible iframe and tricks users into clicking buttons they can't see.
X-Frame-Options: DENY
Options:
DENY— No one can embed your site in an iframe (safest)SAMEORIGIN— Only your own domain can embed it (use if you iframe your own content)
Note: The frame-ancestors directive in CSP is the modern replacement, but X-Frame-Options is still needed for older browser support. Set both.
5. Referrer-Policy
What it controls: How much information is sent in the Referer header when users click links to other sites.
Referrer-Policy: strict-origin-when-cross-origin
This sends the full URL for same-site navigation but only the origin (domain) for cross-site links. This prevents leaking sensitive URL paths (like /account/settings/security) to third-party sites.
| Policy | Same-site | Cross-site | Use Case |
|--------|-----------|------------|----------|
| no-referrer | Nothing | Nothing | Maximum privacy |
| strict-origin-when-cross-origin | Full URL | Origin only | Recommended default |
| origin | Origin only | Origin only | Good balance |
| unsafe-url | Full URL | Full URL | Avoid — leaks paths |
6. Permissions-Policy
What it controls: Which browser features your site can use — camera, microphone, geolocation, payment API, etc.
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=()
Empty parentheses () means the feature is disabled entirely. If your site doesn't use the camera, disable it — this prevents any injected script from accessing it.
Specify allowed origins if you need a feature:
Permissions-Policy: geolocation=(self "https://maps.example.com")
How to Set Security Headers
Nginx
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
The always keyword ensures headers are sent on error pages too (40x, 50x responses), not just successful ones.
Apache (.htaccess)
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "DENY"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Vercel (vercel.json)
{
"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" },
{ "key": "Permissions-Policy", "value": "camera=(), microphone=(), geolocation=()" }
]
}
]
}
Next.js (next.config.js)
const securityHeaders = [
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'X-Frame-Options', value: 'DENY' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
];
module.exports = {
async headers() {
return [{ source: '/(.*)', headers: securityHeaders }];
},
};
Cloudflare
Use Transform Rules (Rules → Transform Rules → Modify Response Header) to add headers to all responses without touching your origin server. This is the easiest approach if you're already using Cloudflare.
Security Header Grades
SecurityHeaders.com grades your site A through F based on which headers are present:
| Grade | What You Have | What You're Missing | |-------|--------------|-------------------| | A+ | All 6 essential headers + CSP | Nothing | | A | 5 headers, basic CSP | Strict CSP or Permissions-Policy | | B | 3-4 headers | CSP and 1-2 others | | C | 1-2 headers | Most protections | | D/F | None or almost none | Critical protections |
Implementation Priority
If you can only add headers in stages, this is the order:
- X-Content-Type-Options: nosniff — One line, zero risk, protects against MIME confusion
- X-Frame-Options: DENY — One line, zero risk (unless you iframe your own site)
- Strict-Transport-Security — Requires working HTTPS first, then one line
- Referrer-Policy — One line, minor privacy improvement
- Permissions-Policy — Disable features you don't use
- Content-Security-Policy — Most impactful but requires testing — use Report-Only first
Test Your Headers
Run your site through our scanner above to check which security headers you're missing and get a security score. The scanner checks all six essential headers plus your SSL configuration, DNS health, and page speed.
Frequently Asked Questions
- Which security headers are most important?
- Start with these three: Strict-Transport-Security (HSTS) to force HTTPS, X-Content-Type-Options to prevent MIME sniffing, and X-Frame-Options to prevent clickjacking. These three cover the most common attack vectors and take seconds to add.
- Will security headers break my website?
- Most headers are safe to add immediately — HSTS, X-Content-Type-Options, X-Frame-Options, and Referrer-Policy won't break anything. Content-Security-Policy (CSP) is the exception — a misconfigured CSP can block legitimate resources. Always test CSP with Content-Security-Policy-Report-Only first.
- How do I check what security headers my site has?
- Use our scanner tool, SecurityHeaders.com, or open Chrome DevTools → Network tab → click any request → look at the Response Headers section. Missing headers are the ones you need to add.
- Do I need security headers on a static website?
- Yes. Even static sites can be targeted by clickjacking (embedding your site in a malicious iframe), protocol downgrade attacks (stripping HTTPS), and content type confusion attacks. The headers take minutes to set up regardless of site type.
Related Articles
SSL Certificates Explained: Types, Setup, and Common Mistakes
Everything website owners need to know about SSL/TLS certificates — from free Let's Encrypt to EV certificates. Plus common SSL mistakes and how to fix them.
Website Speed Optimization: The Complete 2026 Guide
Why your website is slow and exactly how to fix it. Core Web Vitals, image optimization, CDN setup, and more.
DNS Explained: How Domain Names Actually Work
A plain-English explanation of DNS — how your browser turns a domain name into a website. Records, propagation, and troubleshooting.
Check your own website
Run a free scan to check SSL, DNS, speed, and security headers.
Scan Your Site Free →