HTTP security headers are your website's first line of defense. Learn what CSP, HSTS, X-Frame-Options, and other headers do — and how to configure them correctly.
Anay Pandya
1,954 words
Anay Pandya
Founder of ZeriFlow · 10 years fullstack engineering · About the author
Key Takeaways
Every time a user visits your website, their browser and your server exchange a series of HTTP headers. Most of these headers handle routine tasks like caching, content types, and encoding. But a special subset of headers exists purely for security, and they are among the most powerful yet underused defenses available to any website owner.
<div class="zf-stat-callout" style="background:#0d1117;border:1px solid rgba(16,185,129,0.25);border-left:3px solid #10b981;border-radius:4px;padding:16px 20px;margin:24px 0"> <p style="margin:0 0 4px;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:0.15em;color:#10b981;font-family:monospace">ZeriFlow Data — 12,400+ sites analyzed</p> <p style="margin:0;font-size:13px;color:#e2e8f0;line-height:1.6;font-family:monospace">In our analysis of 12,400+ sites scanned on ZeriFlow, 64% lack a Content-Security-Policy header — and of those that have one, 71% use 'unsafe-inline', negating XSS protection entirely.</p> </div>
Keep reading
Is your site actually secure?
Run a free check — 60 seconds
HTTP security headers instruct the browser on how to behave when handling your site''s content. They can prevent cross-site scripting attacks, stop clickjacking, enforce encrypted connections, and block a wide range of client-side exploits. The best part is that they require no changes to your application code. They are pure configuration.
In this guide, we will cover every security header you need to know, explain what each one does, show you how to implement them on popular platforms, and highlight the mistakes that trip up even experienced developers.
Modern web attacks increasingly target the browser. Cross-site scripting (XSS), clickjacking, MIME-type confusion, and data injection attacks all exploit the gap between what your server sends and how the browser interprets it.
Security headers close that gap. They tell the browser exactly what is allowed and what is not. Without them, the browser falls back to permissive defaults that attackers can exploit.
Consider this: a single missing header can allow an attacker to embed your login page inside a malicious iframe, trick users into entering credentials, and steal their accounts. That is a real attack vector that the X-Frame-Options header eliminates in one line of configuration.
Beyond defense, security headers also matter for compliance. Frameworks like PCI DSS, SOC 2, and ISO 27001 increasingly expect proper header configuration as part of a secure web deployment. Search engines also factor security signals into ranking, meaning poor header configuration can hurt your SEO.
Content-Security-Policy is the most powerful and most complex security header. It defines exactly which resources the browser is allowed to load and from which origins.
A properly configured CSP can prevent:
Here is a baseline CSP that you can customize for your site:
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'; base-uri 'self'; form-action 'self'Key directives:
default-src 'self' — only allow resources from your own origin by defaultscript-src 'self' — only allow scripts from your origin (blocks inline scripts and external script injection)frame-ancestors 'none' — prevents your site from being embedded in iframes (replaces X-Frame-Options)base-uri 'self' — prevents base tag hijackingform-action 'self' — prevents form submissions to external domainsCommon mistake: Starting with an overly permissive CSP like default-src * defeats the purpose entirely. Start strict and add exceptions as needed. Use Content-Security-Policy-Report-Only to test before enforcing.
HSTS tells the browser to only connect to your site over HTTPS, even if the user types http:// in the address bar. This prevents SSL stripping attacks where an attacker downgrades the connection to unencrypted HTTP.
Strict-Transport-Security: max-age=31536000; includeSubDomains; preloadParameters:
max-age=31536000 — remember this policy for one year (in seconds)includeSubDomains — apply to all subdomains as wellpreload — request inclusion in browser preload lists so HSTS applies even on the first visitImportant: Only enable HSTS after confirming your entire site works correctly over HTTPS. Once enabled with a long max-age, you cannot easily switch back to HTTP without users being unable to reach your site.
X-Frame-Options prevents your pages from being embedded in iframes on other sites, which is the primary defense against clickjacking attacks.
X-Frame-Options: DENYOptions:
DENY — never allow framing (recommended for most sites)SAMEORIGIN — only allow framing from your own originALLOW-FROM uri — only allow framing from a specific URI (deprecated, use CSP frame-ancestors instead)While CSP frame-ancestors is the modern replacement, X-Frame-Options should still be set for compatibility with older browsers.
This header prevents the browser from MIME-type sniffing, which is when the browser ignores the declared content type and tries to guess the type based on the content. Attackers can exploit this to execute scripts disguised as other file types.
X-Content-Type-Options: nosniffThere is only one valid value. Always set it.
Referrer-Policy controls how much information about the referring page is sent when navigating away from your site. This prevents leaking sensitive URL parameters or internal paths to third-party sites.
Referrer-Policy: strict-origin-when-cross-originRecommended values:
strict-origin-when-cross-origin — sends full referrer for same-origin requests, only the origin for cross-origin requests, and nothing when downgrading from HTTPS to HTTPno-referrer — never send referrer information (most private but may break analytics)same-origin — only send referrer for same-origin requestsPermissions-Policy (formerly Feature-Policy) controls which browser features and APIs your site can use. This prevents malicious scripts from accessing the camera, microphone, geolocation, or other sensitive APIs.
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=()Each directive set to () means the feature is disabled entirely. If your site needs a specific feature, you can allow it with camera=(self) to restrict it to your own origin.
X-DNS-Prefetch-Control:
X-DNS-Prefetch-Control: offPrevents the browser from prefetching DNS for external links, which can leak information about what links are on your page.
Cross-Origin-Opener-Policy (COOP):
Cross-Origin-Opener-Policy: same-originEnsures your document does not share a browsing context group with cross-origin documents, protecting against Spectre-type attacks.
Cross-Origin-Resource-Policy (CORP):
Cross-Origin-Resource-Policy: same-originPrevents other sites from embedding your resources, adding another layer of protection against data leaks.
Add these directives to your server block:
server {
# Security headers
add_header 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'; base-uri 'self'; form-action 'self'" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" 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 "camera=(), microphone=(), geolocation=(), payment=()" always;
add_header X-DNS-Prefetch-Control "off" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Cross-Origin-Resource-Policy "same-origin" always;
}The always parameter ensures headers are sent even on error responses.
Add these to your .htaccess or virtual host configuration:
<IfModule mod_headers.c>
Header always set 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'; base-uri 'self'; form-action 'self'"
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
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 "camera=(), microphone=(), geolocation=(), payment=()"
</IfModule>{
"headers": [
{
"source": "/(.*)",
"headers": [
{ "key": "Content-Security-Policy", "value": "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'" },
{ "key": "Strict-Transport-Security", "value": "max-age=31536000; includeSubDomains; preload" },
{ "key": "X-Frame-Options", "value": "DENY" },
{ "key": "X-Content-Type-Options", "value": "nosniff" },
{ "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" },
{ "key": "Permissions-Policy", "value": "camera=(), microphone=(), geolocation=(), payment=()" }
]
}
]
}addEventListener("fetch", event => {
event.respondWith(handleRequest(event.request));
});
async function handleRequest(request) {
const response = await fetch(request);
const newHeaders = new Headers(response.headers);
newHeaders.set("Content-Security-Policy", "default-src 'self'; script-src 'self'; frame-ancestors 'none'");
newHeaders.set("Strict-Transport-Security", "max-age=31536000; includeSubDomains; preload");
newHeaders.set("X-Frame-Options", "DENY");
newHeaders.set("X-Content-Type-Options", "nosniff");
newHeaders.set("Referrer-Policy", "strict-origin-when-cross-origin");
newHeaders.set("Permissions-Policy", "camera=(), microphone=(), geolocation=()");
return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: newHeaders
});
}1. Setting headers but never testing them. A misconfigured CSP can break your entire site. Always test in report-only mode first, then check for breakage before enforcing.
2. Forgetting error pages. Many servers do not send security headers on 404 or 500 error pages. Use the always directive in Nginx to fix this.
3. Using deprecated header values. X-Frame-Options: ALLOW-FROM is deprecated. Use CSP frame-ancestors instead.
4. Setting conflicting headers. If you set both X-Frame-Options and CSP frame-ancestors, modern browsers will ignore X-Frame-Options in favor of CSP. Make sure they agree.
5. Overly permissive CSP. Directives like script-src 'unsafe-inline' 'unsafe-eval' effectively disable CSP protection for scripts. Use nonces or hashes instead.
6. Missing the Strict-Transport-Security header on subdomains. If you use includeSubDomains, make sure all subdomains actually support HTTPS first.
7. Not removing the Server header. While not a security header per se, the Server header leaks information about your web server software and version. Remove or obscure it.
Manual testing is slow and error-prone. Use automated scanning to check your headers regularly.
ZeriFlow scans your website and checks all security headers automatically, giving you a clear score and actionable recommendations. Run a free scan now to see exactly which headers you are missing and how to fix them.
For ongoing monitoring, the ZeriFlow advanced scan provides deep analysis of your header configuration, including CSP policy evaluation, HSTS preload readiness, and compatibility checks across browsers.
Security headers are one of the highest-impact, lowest-effort security improvements you can make. They require no code changes, protect against real attack vectors, and can be implemented in minutes.
Here is your action plan:
Do not leave your users unprotected. Security headers take minutes to implement and provide lasting defense against some of the most common web attacks.
<!-- zf-internal-links -->
Check all your security headers instantly — free.
See exactly which headers are missing and how to fix them.
Related resources
Website Vulnerability Scanner
Run a broader website security audit across headers, TLS, DNS, cookies, SEO, and disclosure checks.
Security Headers Checker
Check CSP, HSTS, X-Frame-Options, and other response headers.
SSL Checker
Review TLS certificate, HTTPS, and transport security signals.
DMARC Checker
Validate email authentication records for domain spoofing protection.
CSP Checker
Review Content-Security-Policy coverage and common gaps.
March 16, 2026
SSL Labs Alternative: What SSL Labs Tests and What It Doesn't Tell You
Monitor your security score
Schedule recurring scans and catch regressions after deployments.