7 Security Headers Every Website Needs (And How to Add Them)
HTTP security headers are one of the easiest, most impactful things you can do to harden a website. They cost nothing, take minutes to add, and protect against entire classes of attacks. Here is every header you need, what it does, and how to deploy it on any server.
Why Security Headers Matter
Every time a browser loads your site, it reads HTTP response headers to decide what it is and is not allowed to do. Without the right headers, browsers fall back to permissive defaults: they will happily embed your page in an attacker's iframe, execute inline scripts from injected markup, or sniff a text file as executable JavaScript.
Security headers close these gaps. They tell browsers to enforce HTTPS, block framing, restrict script sources, and refuse to guess content types. The attacks they prevent are not theoretical -- clickjacking, cross-site scripting (XSS), MIME confusion, protocol downgrade, and data exfiltration are among the most commonly exploited vulnerabilities on the web.
The best part: adding security headers does not require changing your application code. They are server-level configuration. You can go from zero headers to a hardened setup in under ten minutes. If you are unfamiliar with how domain security scoring works more broadly, see our guide on understanding your domain security score.
Quick Reference: All 7 Headers at a Glance
| Header | Protects Against | Recommended Value |
|---|---|---|
| Strict-Transport-Security | Protocol downgrade, cookie hijacking | max-age=31536000; includeSubDomains; preload |
| Content-Security-Policy | XSS, data injection, clickjacking | default-src 'self'; script-src 'self' (customize per site) |
| X-Frame-Options | Clickjacking | DENY |
| X-Content-Type-Options | MIME sniffing | nosniff |
| Permissions-Policy | Unwanted API access (camera, mic, geolocation) | camera=(), microphone=(), geolocation=() |
| Referrer-Policy | URL data leakage | strict-origin-when-cross-origin |
| X-XSS-Protection | Reflected XSS (legacy) | 0 (deprecated -- see notes below) |
The 7 Essential Security Headers
1. Strict-Transport-Security (HSTS)
What it does: HSTS tells browsers to only connect to your site over HTTPS, even if a user types http:// or clicks an HTTP link. Without it, the first request to your site may go over plain HTTP, giving attackers a window to intercept traffic via a man-in-the-middle attack.
Recommended value:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
max-age=31536000 means the browser will remember to use HTTPS for one year. includeSubDomains applies the policy to all subdomains. preload makes your site eligible for the HSTS preload list, which is hardcoded into browsers so even the very first visit is forced to HTTPS.
Important: Only enable HSTS after you have confirmed that HTTPS works correctly across your entire domain and all subdomains. Once the browser caches this header, HTTP access is blocked for the duration of
max-age.
2. Content-Security-Policy (CSP)
What it does: Content-Security-Policy is the single most powerful security header. It defines exactly which sources the browser is allowed to load scripts, styles, images, fonts, frames, and other resources from. A well-configured CSP can neutralize most XSS attacks entirely.
Recommended starting value:
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'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'
CSP is not a one-size-fits-all header. The value above is a reasonable starting point, but you will almost certainly need to customize it based on your site's dependencies. If you use Google Analytics, a third-party chat widget, or a CDN for scripts, you need to whitelist those origins explicitly.
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 eval)frame-ancestors 'none'-- prevents your site from being framed (replaces X-Frame-Options)base-uri 'self'-- prevents base tag injection attacksform-action 'self'-- restricts where forms can submit data
If you are not ready for a full CSP, start with Content-Security-Policy-Report-Only to monitor violations without blocking anything. This lets you identify what would break before enforcing the policy.
3. X-Frame-Options
What it does: X-Frame-Options tells the browser whether your page is allowed to be displayed inside an <iframe>. This prevents clickjacking attacks, where an attacker overlays your site inside an invisible frame and tricks users into clicking hidden buttons.
Recommended value:
X-Frame-Options: DENY
Use DENY to block all framing. If your site legitimately needs to be embedded in iframes on your own domain, use SAMEORIGIN instead. Note that CSP's frame-ancestors directive is the modern replacement for X-Frame-Options, but you should set both for backward compatibility with older browsers.
4. X-Content-Type-Options
What it does: By default, browsers perform MIME sniffing -- they try to guess the content type of a file by inspecting its contents rather than trusting the Content-Type header. An attacker can exploit this by uploading a file that looks like HTML or JavaScript, even if it has a harmless extension. The browser will execute it.
Recommended value:
X-Content-Type-Options: nosniff
This is the simplest header on the list. There is only one valid value (nosniff), it has no side effects, and it should be on every single response. There is no reason to leave it off.
5. Permissions-Policy
What it does: Permissions-Policy (formerly Feature-Policy) controls which browser APIs and features your site can use. You can disable access to the camera, microphone, geolocation, payment APIs, and many other sensitive capabilities.
Recommended value:
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=()
The empty parentheses () mean "this feature is disabled for all origins, including your own." If your site does use geolocation or camera access, replace () with (self) for those specific features. The key principle is: disable everything you do not use.
6. Referrer-Policy
What it does: When a user clicks a link from your site to another site, the browser sends a Referer header containing the URL they came from. This can leak sensitive information -- query parameters, internal page paths, and user-specific URLs can all end up in third-party access logs.
Recommended value:
Referrer-Policy: strict-origin-when-cross-origin
This setting sends the full URL for same-origin requests (useful for your own analytics), sends only the origin (e.g., https://example.com) for cross-origin requests over HTTPS, and sends nothing when navigating from HTTPS to HTTP. It is the best balance between functionality and privacy.
7. X-XSS-Protection (Bonus / Legacy)
What it does: X-XSS-Protection was an early attempt by browsers to detect and block reflected XSS attacks. It activated a built-in XSS filter in the browser that would scan responses for suspicious patterns.
Recommended value:
X-XSS-Protection: 0
Why set it to 0? This header is deprecated. Chrome removed its XSS Auditor in 2019, and no modern browser implements it anymore. Worse, the old filter itself had vulnerabilities -- attackers could use it to selectively disable legitimate scripts on a page. The current best practice is to explicitly set X-XSS-Protection: 0 to ensure the filter is disabled in any legacy browser that might still support it, and rely on Content-Security-Policy for XSS protection instead.
You will still see this header recommended with a value of 1; mode=block in older guides. That advice is outdated. Set it to 0 and move on.
How do your headers stack up?
Scanward checks all 6 critical security headers and gives you an instant score. Find out what you are missing in seconds.
Scan Your Domain Free →How to Add Security Headers
The implementation varies depending on your server or hosting setup. Below are copy-paste configurations for the most common environments.
Nginx
Add these directives inside your server block:
# /etc/nginx/sites-available/yoursite.conf
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
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 X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header X-XSS-Protection "0" always;
The always keyword ensures headers are sent even on error responses (4xx, 5xx). Without it, Nginx only adds headers to successful responses.
Apache / .htaccess
Add these lines to your .htaccess file or your virtual host configuration:
# .htaccess or httpd.conf
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
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 X-Frame-Options "DENY"
Header always set X-Content-Type-Options "nosniff"
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set X-XSS-Protection "0"
Make sure mod_headers is enabled. On Debian/Ubuntu, run sudo a2enmod headers and restart Apache.
Cloudflare
If you use Cloudflare, you have two options:
Option 1: Transform Rules (recommended). Go to Rules > Transform Rules > Modify Response Header. Add each header as a static header. This method requires no code changes and works even for static sites.
Option 2: Cloudflare Workers. For more control, use a Cloudflare Worker to add headers programmatically:
// Cloudflare Worker
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request));
});
async function handleRequest(request) {
const response = await fetch(request);
const newResponse = new Response(response.body, response);
newResponse.headers.set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
newResponse.headers.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'");
newResponse.headers.set('X-Frame-Options', 'DENY');
newResponse.headers.set('X-Content-Type-Options', 'nosniff');
newResponse.headers.set('Permissions-Policy', 'camera=(), microphone=(), geolocation=(), payment=()');
newResponse.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
newResponse.headers.set('X-XSS-Protection', '0');
return newResponse;
}
Node.js / Express
The cleanest approach is the Helmet middleware, which sets sensible security headers by default:
const express = require('express');
const helmet = require('helmet');
const app = express();
// Helmet sets most security headers with good defaults
app.use(helmet());
// Or configure each header individually:
app.use(helmet({
strictTransportSecurity: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
},
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
fontSrc: ["'self'"],
connectSrc: ["'self'"],
frameAncestors: ["'none'"],
baseUri: ["'self'"],
formAction: ["'self'"]
}
},
frameguard: { action: 'deny' },
noSniff: true,
referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
xssFilter: false // Disables X-XSS-Protection (sets to 0)
}));
If you prefer not to use Helmet, you can set headers manually with middleware:
app.use((req, res, next) => {
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
res.setHeader('Content-Security-Policy', "default-src 'self'; script-src 'self'");
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('Permissions-Policy', 'camera=(), microphone=(), geolocation=(), payment=()');
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
res.setHeader('X-XSS-Protection', '0');
next();
});
Testing Your Headers
After deploying headers, you need to verify they are actually being sent. There are several ways to check:
Scanward. Run your domain through the Scanward scanner and you will get an instant breakdown of which security headers are present, which are missing, and what your overall domain security score looks like. It checks all six critical headers (HSTS, CSP, X-Frame-Options, X-Content-Type-Options, Permissions-Policy, and Referrer-Policy) and flags misconfiguration.
Browser DevTools. Open your site, go to the Network tab, click on the initial document request, and check the Response Headers section. Every header you added should be visible there.
curl. From the command line, you can inspect headers directly:
curl -I https://yourdomain.com
This returns only the response headers, making it easy to confirm your configuration is working.
securityheaders.com. Scott Helme's securityheaders.com provides a letter grade based on your headers. It is a good complement to Scanward's broader security assessment, which also covers email authentication (SPF, DKIM, and DMARC), SSL/TLS, and DNS configuration.
Common Pitfalls
Security headers are simple in concept, but there are a few mistakes that catch people regularly:
CSP Too Restrictive, Breaking Your Own Site
This is the most common issue. You deploy a strict Content-Security-Policy, and suddenly your analytics stop working, your chat widget disappears, or your fonts fail to load. Always start with Content-Security-Policy-Report-Only and monitor the browser console for violations before switching to enforcement mode. Roll out CSP gradually -- do not go from nothing to a strict policy in one step.
Enabling HSTS Before SSL is Ready
If you turn on HSTS before every page and subdomain on your site is properly serving over HTTPS, you will lock users out. They will be unable to reach your site over HTTP, and if your HTTPS is broken, they will see a certificate error with no way to bypass it. Start with a short max-age (e.g., max-age=300 for five minutes), verify everything works, then increase to the full year.
Forgetting Error Pages and Redirects
In Nginx, add_header without the always keyword only applies to 2xx and 3xx responses. Your 404 and 500 error pages will be served without security headers, which is still a vulnerability. Always use the always parameter.
Headers Being Overridden by a CDN or Proxy
If you are behind Cloudflare, AWS CloudFront, or another CDN, your origin server headers might be stripped or overwritten. After deploying headers, test from the actual public URL -- not just your origin server -- to confirm the CDN is passing them through. Some CDNs require explicit configuration to forward custom headers.
Setting X-XSS-Protection to 1
As covered above, the XSS filter is deprecated and was itself a source of vulnerabilities. Setting X-XSS-Protection: 1; mode=block in 2026 is actively worse than not setting it at all. Use 0 and rely on CSP.
Not Updating CSP When Adding New Third-Party Services
Your CSP is only correct at the moment you write it. Every time you add a new analytics provider, embed a video, or integrate a payment form, you need to update your CSP to whitelist those new origins. Build CSP reviews into your deployment checklist.
Summary
Adding HTTP security headers is one of the highest-impact, lowest-effort improvements you can make to your site's security posture. The six core headers -- HSTS, CSP, X-Frame-Options, X-Content-Type-Options, Permissions-Policy, and Referrer-Policy -- cover the vast majority of browser-side attack vectors. Combined with a well-configured email authentication setup, they form the foundation of a strong domain security posture.
The configuration is copy-paste for most servers. The testing is free. There is no good reason not to do it.
Check your security headers now
Scanward checks all 6 critical security headers and gives you a score in seconds -- completely free.
Scan Your Domain Free →