Why Security Headers Matter
HTTP security headers are instructions your server sends to browsers, telling them how to behave when handling your site's content. They are your first line of defense against common web attacks: cross-site scripting (XSS), clickjacking, MIME-type sniffing, and protocol downgrade attacks.
The problem? Most websites are missing some or all of them. A 2025 study by the HTTP Archive found that only 12% of websites serve a Content-Security-Policy header, and only 25% serve Strict-Transport-Security.
These headers cost nothing to implement, require no code changes, and can be deployed in minutes. There is no reason not to have them.
The 6 Headers You Must Have
Here are the six security headers every website needs, in order of importance:
1. Strict-Transport-Security (HSTS)
Forces HTTPS connections. Prevents protocol downgrade attacks.
Strict-Transport-Security: max-age=31536000; includeSubDomains; preloadmax-age=31536000— enforce for one yearincludeSubDomains— applies to all subdomainspreload— submit to browser preload lists
2. Content-Security-Policy (CSP)
Controls which resources the browser is allowed to load. The single most effective header against XSS attacks.
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';A strict CSP looks like this:
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self'; font-src 'self'; connect-src 'self'; base-uri 'self'; form-action 'self'; frame-ancestors 'none';Start with report-only mode to avoid breaking your site:
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report;3. X-Content-Type-Options
Prevents MIME-type sniffing. Browsers will only use the Content-Type you declare.
X-Content-Type-Options: nosniffOne value, one purpose. This is the easiest header to add.
4. X-Frame-Options
Prevents your site from being embedded in iframes on other domains (clickjacking protection).
X-Frame-Options: DENYUse DENY to block all framing, or SAMEORIGIN to allow framing only by your own domain.
Note: frame-ancestors in CSP supersedes X-Frame-Options, but include both for backward compatibility with older browsers.
5. Referrer-Policy
Controls how much referrer information is shared when users navigate away from your site.
Referrer-Policy: strict-origin-when-cross-originThis sends the full URL for same-origin requests and only the origin (domain) for cross-origin requests. It is a good balance between privacy and functionality.
6. Permissions-Policy
Controls which browser features your site can use (camera, microphone, geolocation, etc.).
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()Empty parentheses () disable the feature entirely. If your site does not use the camera, declare it — this prevents malicious scripts from accessing it if they somehow execute on your page.
How to Add Headers on Nginx
Add all six headers to your server block:
server {
listen 443 ssl http2;
server_name yoursite.com;
# Security Headers
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';" 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;
# SSL config
ssl_certificate /etc/ssl/certs/yoursite.crt;
ssl_certificate_key /etc/ssl/private/yoursite.key;
location / {
# ... your config
}
}Important Nginx caveat: If you use add_header inside a location block, it overrides all headers set at the server level. Either set all headers at the server level, or repeat them in every location block. Alternatively, use the ngx_headers_more module:
more_set_headers "X-Content-Type-Options: nosniff";Test and reload:
nginx -t && systemctl reload nginxHow to Add Headers on Apache
Enable mod_headers and add the headers to your VirtualHost or .htaccess:
<IfModule mod_headers.c>
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';"
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=()"
</IfModule>Enable the module if it is not already active:
a2enmod headers
systemctl restart apache2How to Add Headers on Cloudflare
Cloudflare offers multiple approaches:
Transform Rules (easiest)
- 1Go to Rules → Transform Rules → Modify Response Header
- 2Create a new rule matching all traffic (
*) - 3Add each header as a "Set static" action
Cloudflare Workers (most flexible)
export default {
async fetch(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';");
newResponse.headers.set('X-Content-Type-Options', 'nosniff');
newResponse.headers.set('X-Frame-Options', 'DENY');
newResponse.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
newResponse.headers.set('Permissions-Policy', 'camera=(), microphone=(), geolocation=(), payment=()');
return newResponse;
},
};HSTS specifically
Cloudflare has a dedicated HSTS toggle under SSL/TLS → Edge Certificates. Use it for HSTS and Transform Rules for the rest.
How to Add Headers on Vercel / Next.js
In next.config.js:
/** @type {import('next').NextConfig} */
const nextConfig = {
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'Strict-Transport-Security',
value: 'max-age=31536000; includeSubDomains; preload',
},
{
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: '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=(), payment=()',
},
],
},
];
},
};
module.exports = nextConfig;Alternatively, use middleware for dynamic CSP with nonces:
// middleware.ts
import { NextResponse } from 'next/server';
export function middleware(request) {
const nonce = Buffer.from(crypto.randomUUID()).toString('base64');
const csp = `default-src 'self'; script-src 'self' 'nonce-${nonce}'; style-src 'self' 'unsafe-inline';`;
const response = NextResponse.next();
response.headers.set('Content-Security-Policy', csp);
response.headers.set('X-Content-Type-Options', 'nosniff');
response.headers.set('X-Frame-Options', 'DENY');
response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
response.headers.set('Permissions-Policy', 'camera=(), microphone=(), geolocation=(), payment=()');
return response;
}How to Verify Your Headers
Command line
curl -sI https://yoursite.com | grep -iE '(strict-transport|content-security|x-content-type|x-frame|referrer-policy|permissions-policy)'Quick verification table
| Header | Expected value | Check |
|---|---|---|
| Strict-Transport-Security | max-age=31536000; ... | Present and max-age >= 31536000 |
| Content-Security-Policy | default-src 'self'; ... | Present and not empty |
| X-Content-Type-Options | nosniff | Exact match |
| X-Frame-Options | DENY or SAMEORIGIN | Present |
| Referrer-Policy | strict-origin-when-cross-origin | Present |
| Permissions-Policy | camera=(), ... | Present |
Automated scanning
The fastest way to check all headers at once is to run a scan at zeriflow.com. The scanner checks all six headers plus additional security configurations, and gives you specific remediation steps for anything that is missing.
Next Steps
After adding these headers:
- 1Test your site thoroughly — CSP in particular can break functionality if too restrictive
- 2Monitor CSP violations — Use
report-uriorreport-todirectives to catch issues - 3Increase HSTS max-age gradually — Start small, then scale up
- 4Run a full security scan — Headers are just one part of your security posture. Run a comprehensive scan at zeriflow.com to check SSL/TLS, cookies, DNS, and more
Security headers are the lowest-effort, highest-impact security improvement you can make to any website. There is no reason to leave them missing.
