Security Headers: The Complete Guide to Protecting Your Website
HTTP security headers are your first line of defense against XSS, clickjacking, data injection, and protocol downgrade attacks. They cost nothing to implement, require no code changes, and can be deployed in minutes. Yet over 90% of websites are missing at least one critical header.
Why Security Headers Matter
When a browser loads your website, it follows instructions embedded in HTTP response headers. These headers tell the browser which scripts to trust, whether to allow framing, how to handle content types, and whether to enforce HTTPS. Without them, the browser falls back to permissive defaults that leave your users vulnerable to a wide range of attacks.
Security headers are a defense-in-depth measure. They do not replace secure code, but they add a layer of protection that mitigates the impact of vulnerabilities. A properly configured Content-Security-Policy can prevent an XSS vulnerability from being exploitable. HSTS ensures that even if an attacker manages a man-in-the-middle position, they cannot downgrade the connection to HTTP.
The best part: implementing security headers is a server configuration change. No application code needs to be modified. You can add all the headers described in this guide in under 30 minutes.
Strict-Transport-Security (HSTS)
What it does: Tells the browser to only connect to your site over HTTPS, even if the user types http://. Once the browser sees this header, it will automatically convert all HTTP requests to HTTPS for the specified duration.
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Parameters explained:
max-age=31536000— Enforce HTTPS for 1 year (in seconds). Start with a smaller value (e.g., 300) during testing.includeSubDomains— Apply HSTS to all subdomains. Important: only add this if ALL subdomains support HTTPS.preload— Opt into the HSTS Preload List, which is hardcoded into browsers. This protects against the very first connection (before the header is seen). Submit your domain athstspreload.org.
What it prevents: Protocol downgrade attacks (SSL stripping), where an attacker intercepts the initial HTTP request before the redirect to HTTPS. Without HSTS, the very first request to your site (before the 301 redirect) is sent over plain HTTP and is vulnerable to interception.
Start with
max-age=300to test. If everything works, increase tomax-age=31536000. HSTS mistakes are hard to undo — a bad HSTS header can make your site unreachable if your HTTPS breaks.
Content-Security-Policy (CSP)
What it does: Defines which sources of content (scripts, styles, images, fonts, frames, etc.) the browser should trust. Any content from unlisted sources is blocked. This is the most powerful security header and the most complex to configure.
# Strict CSP example
Content-Security-Policy:
default-src 'self';
script-src 'self' https://cdn.example.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
upgrade-insecure-requests;
Key directives:
default-src 'self'— Only allow resources from your own origin by defaultscript-src— Controls where JavaScript can be loaded from. Avoid'unsafe-inline'and'unsafe-eval'— these defeat the purpose of CSP. Use nonces or hashes instead.style-src— Controls stylesheets.'unsafe-inline'is often needed for CSS but consider moving to nonces.img-src— Controls image sources.data:allows base64-encoded images.frame-ancestors 'none'— Prevents your site from being embedded in iframes (replaces X-Frame-Options).upgrade-insecure-requests— Automatically upgrades HTTP requests to HTTPS.
Use our CSP Builder to generate a Content-Security-Policy interactively without memorizing the directive syntax.
CSP Reporting
Deploy CSP in report-only mode first to identify what would break without actually blocking anything:
# Report-only mode (does not block, just reports violations)
Content-Security-Policy-Report-Only:
default-src 'self';
script-src 'self';
report-uri /csp-report-endpoint;
Monitor the reports, adjust your policy to allow legitimate resources, then switch from Content-Security-Policy-Report-Only to Content-Security-Policy to enforce it.
Check Your Security Headers Instantly
Our free Exposure Checker analyzes all your HTTP security headers and tells you exactly which ones are missing, misconfigured, or properly set.
Check Your Headers NowX-Frame-Options
What it does: Controls whether your site can be embedded in an <iframe>. Prevents clickjacking attacks where an attacker overlays a transparent iframe of your site over a malicious page, tricking users into clicking buttons they cannot see.
# Block all framing
X-Frame-Options: DENY
# Allow framing only from your own origin
X-Frame-Options: SAMEORIGIN
Note: The frame-ancestors directive in CSP is the modern replacement for X-Frame-Options and is more flexible (supports specific domains). However, X-Frame-Options should still be set for browsers that do not fully support CSP.
X-Content-Type-Options
What it does: Prevents the browser from MIME-sniffing a response away from the declared Content-Type. Without this header, a browser might interpret a plain text file as JavaScript if it contains code-like content, enabling XSS through file uploads.
X-Content-Type-Options: nosniff
There is only one valid value: nosniff. This header should be on every response. There is no downside to adding it and no reason to omit it.
Referrer-Policy
What it does: Controls how much referrer information (the URL of the previous page) is included when navigating away from your site or loading external resources. By default, the full URL (including query parameters that might contain sensitive data) is sent to third parties.
# Recommended: send origin only when going cross-origin
Referrer-Policy: strict-origin-when-cross-origin
# Most restrictive: never send referrer
Referrer-Policy: no-referrer
# Other options
Referrer-Policy: no-referrer-when-downgrade # default browser behavior
Referrer-Policy: same-origin # only on same-origin navigation
Referrer-Policy: origin # always send just the origin
Recommended value: strict-origin-when-cross-origin. This sends the full URL for same-origin requests (useful for analytics), the origin only for cross-origin HTTPS requests, and nothing when downgrading from HTTPS to HTTP.
Permissions-Policy (formerly Feature-Policy)
What it does: Controls which browser features (camera, microphone, geolocation, payment, USB, etc.) your site can use. This prevents malicious scripts (injected via XSS or third-party ads) from accessing sensitive device features without your knowledge.
# Deny all sensitive features by default
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=()
# Allow specific features for your origin only
Permissions-Policy: camera=(self), geolocation=(self "https://maps.example.com")
Key features to restrict:
camera=()— Block camera accessmicrophone=()— Block microphone accessgeolocation=()— Block location trackingpayment=()— Block Payment Request APIusb=()— Block USB device accessinterest-cohort=()— Opt out of FLoC/Topics API (Google ad tracking)
Additional Recommended Headers
Cache-Control
For pages containing sensitive data, prevent caching:
Cache-Control: no-store, no-cache, must-revalidate, private
X-Request-Id
Include a unique request ID for tracing and debugging:
X-Request-Id: 550e8400-e29b-41d4-a716-446655440000
Cross-Origin Headers
# Prevent your site from being opened in a cross-origin context
Cross-Origin-Opener-Policy: same-origin
# Prevent your resources from being loaded cross-origin
Cross-Origin-Resource-Policy: same-origin
# Required for SharedArrayBuffer and high-resolution timers
Cross-Origin-Embedder-Policy: require-corp
Implementation: How to Set Security Headers
Nginx
# /etc/nginx/conf.d/security-headers.conf
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" 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=(), payment=()" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; frame-ancestors 'none';" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Cross-Origin-Resource-Policy "same-origin" always;
Use our Nginx Config Generator to create a complete Nginx configuration with security headers included.
Apache
# .htaccess or httpd.conf
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
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"
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()"
Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; frame-ancestors 'none';"
Express.js (Node.js)
// Using helmet middleware (recommended)
const helmet = require('helmet');
app.use(helmet());
// Or set headers manually
app.use((req, res, next) => {
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
res.setHeader('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');
res.setHeader('Content-Security-Policy', "default-src 'self'");
next();
});
Cloudflare
If you use Cloudflare, you can add security headers via Transform Rules (Modify Response Header) without touching your origin server. This is useful for adding headers to static sites hosted on platforms you do not control.
Testing Your Security Headers
After implementing headers, verify them using multiple methods:
# Using curl to check response headers
curl -I https://yourdomain.com
# Check specific headers
curl -sI https://yourdomain.com | grep -iE "(strict-transport|content-security|x-frame|x-content-type|referrer-policy|permissions-policy)"
For a comprehensive automated check, use the SecureBin Exposure Checker — it tests all security headers and provides specific remediation guidance for each missing or misconfigured header. You can also use our SSL Checker to verify that your HTTPS configuration is solid before adding HSTS.
Common Mistakes to Avoid
- Setting HSTS with
includeSubDomainsbefore verifying all subdomains support HTTPS. This can make subdomains unreachable. Test every subdomain first. - Using
Content-Security-Policy: default-src *. This is equivalent to having no CSP at all. Start with'self'and add specific trusted sources. - Using
'unsafe-inline'and'unsafe-eval'in script-src. These directives negate most of CSP's XSS protection. Use nonces ('nonce-randomvalue') or hashes instead. - Setting headers on HTML pages only. Use the
alwayskeyword in Nginx/Apache to apply headers to all response codes (including 404 and 500 errors). - Forgetting to test after deployment. A syntax error in a CSP header can break your entire site. Always test in report-only mode first.
Frequently Asked Questions
Do security headers affect website performance?
No. Security headers add a negligible amount of data to HTTP responses (typically under 500 bytes). They are processed by the browser during header parsing, which takes microseconds. HSTS actually improves performance by eliminating the HTTP-to-HTTPS redirect on subsequent visits. CSP can slightly improve page load times by preventing unauthorized resource loading.
Will adding security headers break my website?
CSP is the only header that commonly causes issues, because it blocks resources from unauthorized sources. If your site loads scripts, fonts, or styles from third-party CDNs, you need to explicitly allow those domains in your CSP. All other headers (HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy) rarely cause problems. Always deploy CSP in Report-Only mode first.
I use a CDN/WAF (Cloudflare, Fastly, etc.). Where should I set headers?
Set headers at the CDN/WAF level for consistency and to ensure they apply to cached responses. Most CDNs allow you to add or override response headers via their dashboard or configuration. If you set headers at both the origin and CDN, the CDN may duplicate or override the origin headers depending on the configuration — test to verify.
What is the minimum set of security headers I should implement?
At minimum, every website should have: Strict-Transport-Security, X-Content-Type-Options: nosniff, X-Frame-Options: DENY, and Referrer-Policy: strict-origin-when-cross-origin. These four headers take 5 minutes to add and protect against the most common attacks. Add Content-Security-Policy and Permissions-Policy as soon as you can invest the testing time.
Grade Your Security Headers
Run your domain through our Exposure Checker to get a complete security header analysis with specific fix instructions for each missing header.
Check Your Headers FreeThe Bottom Line
Security headers are the highest-impact, lowest-effort security improvement you can make to any website. They require no code changes, cost nothing, deploy in minutes, and protect against entire categories of attacks. Start with the four essential headers (HSTS, X-Content-Type-Options, X-Frame-Options, Referrer-Policy), then add CSP and Permissions-Policy. Test with the Exposure Checker, fix what is missing, and re-test.
Related tools: Exposure Checker, CSP Builder, SSL Checker, Nginx Config Generator, Meta Tag Generator, and 70+ more free tools.