Antoine Duno
Founder of ZeriFlow · 10 years fullstack engineering · About the author
Key Takeaways
- HTTP security headers are free, fast to deploy, and fix a wide range of browser-level vulnerabilities. This guide covers all seven essential headers, what each one does, how to add them to your server, and how to verify they are working.
- Includes copy-paste code examples and step-by-step instructions.
- Free automated scan available to verify your implementation.
HTTP Security Headers Explained: The Complete Developer Guide (2026)
HTTP security headers are one of the highest-leverage security improvements you can make to any web application. They are free, they do not require application code changes in most cases, and they can be deployed in under an hour. Yet a significant majority of production websites are missing at least three of the seven core headers.
This guide walks through every header you need, explains exactly what it does, and shows you how to add it — with copy-paste examples for Nginx, Apache, and Node.js/Express.
Why Security Headers Matter
Browsers have built-in security mechanisms that are disabled by default. Security headers are the server-side instructions that activate those mechanisms for your site. Without them:
- Attackers can load your site in an iframe and trick users into clicking hidden buttons (clickjacking)
- Malicious scripts injected via XSS can load resources from any domain
- Browsers may sniff MIME types and execute a text file as JavaScript
- Your users'' traffic can be downgraded from HTTPS to HTTP mid-connection
None of these are application bugs. They are gaps in browser security that headers close.
How to Check Your Current Security Headers
Before you start adding headers, check what you already have. You can do this in several ways:
Browser DevTools: Open Network tab, click any request to your domain, look at the Response Headers section.
Command line:
curl -I https://yourdomain.comAutomated scan: Run a free check at ZeriFlow — it tests all headers simultaneously and shows which are missing, misconfigured, or outdated.
The 7 Essential HTTP Security Headers
1. Content-Security-Policy (CSP)
What it does: Tells the browser exactly which sources are allowed to load scripts, styles, images, fonts, and other resources. Any resource that does not match the policy is blocked. This is the primary defence against Cross-Site Scripting (XSS) attacks.
Basic example:
Content-Security-Policy: default-src ''self''; script-src ''self'' https://cdn.yourdomain.com; style-src ''self'' ''unsafe-inline''; img-src ''self'' data:; frame-ancestors ''none''Safe starting point (report-only, won''t break anything):
Content-Security-Policy-Report-Only: default-src ''self''; report-uri /csp-report-endpointKey directives:
- default-src — fallback for all resource types
- script-src — controls JavaScript sources
- style-src — controls CSS sources
- img-src — controls image sources
- frame-ancestors — controls which pages can embed yours in an iframe (supersedes X-Frame-Options in modern browsers)
- report-uri / report-to — where to send violation reports
CSP is covered in depth in the dedicated CSP guide on this blog.
2. Strict-Transport-Security (HSTS)
What it does: Tells browsers to only access your site over HTTPS, even if the user types http://. Once a browser has seen an HSTS header, it will automatically upgrade all future requests to HTTPS for the duration of max-age — without ever making an HTTP request first.
This eliminates SSL stripping attacks, where a man-in-the-middle intercepts the initial HTTP request before it can redirect to HTTPS.
Recommended configuration:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preloadmax-age=31536000— 1 year in seconds; this is the minimum for HSTS preload submissionincludeSubDomains— applies HSTS to all subdomainspreload— signals intent to be included in browser preload lists (requires separate submission at hstspreload.org)
Do not set this on a site that is not fully on HTTPS. It will lock your users out of HTTP on that domain for the max-age duration.
3. X-Frame-Options
What it does: Prevents your page from being embedded in an <iframe> or <frame> on another domain. This blocks clickjacking attacks, where an attacker overlays a transparent iframe over a decoy page to capture clicks.
Values:
X-Frame-Options: DENY # Never allow framing
X-Frame-Options: SAMEORIGIN # Only allow framing by same originALLOWFROM was a third option (allow a specific origin) but it was never consistently implemented and is now deprecated. If you need to allow specific origins to frame your content, use CSP''s frame-ancestors directive instead.
Note: CSP frame-ancestors supersedes X-Frame-Options in browsers that support it, but you should set both for backwards compatibility.
4. X-Content-Type-Options
What it does: Prevents browsers from guessing (sniffing) the MIME type of a response when it differs from the declared Content-Type. Without this header, a browser might execute a text file as JavaScript if it detects JavaScript-like content — a technique used in MIME confusion attacks.
The value is always:
X-Content-Type-Options: nosniffThis header has zero downside and takes five seconds to add. There is no reason any site should be missing it.
5. Referrer-Policy
What it does: Controls how much of the URL in the Referer (yes, it''s historically misspelt) header is sent to third parties when a user navigates from your page to an external site. Without a policy, the full URL — including query parameters, which may contain user IDs, session tokens, or search terms — is sent with every outbound request.
Common values and their meanings:
| Value | What gets sent externally |
|---|---|
no-referrer | Nothing |
no-referrer-when-downgrade | Full URL to HTTPS, nothing to HTTP (browser default) |
same-origin | Full URL only to same origin; nothing cross-origin |
strict-origin | Just the origin (no path) to HTTPS; nothing to HTTP |
strict-origin-when-cross-origin | Full URL same-origin; just origin cross-origin |
Recommended:
Referrer-Policy: strict-origin-when-cross-originThis is a good balance — it preserves referrer information for same-origin analytics while preventing path and query parameter leakage to third parties.
6. Permissions-Policy
What it does: Allows you to explicitly disable browser features that your site does not use — things like the camera, microphone, geolocation, payment APIs, USB access, and more. Even if an XSS attack injects malicious JavaScript, it cannot access features you have blocked at the policy level.
Example (restrictive policy for a typical SaaS):
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=(), fullscreen=(self)An empty () means the feature is disabled entirely. (self) means only the current origin can use it.
This header replaced the older Feature-Policy header. If you are still using Feature-Policy, replace it.
7. Cross-Origin-Opener-Policy (COOP)
What it does: Isolates your page''s browsing context from third-party popup windows. This is important for defence against Spectre-class side-channel attacks and cross-origin information leakage.
Values:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Opener-Policy: same-origin-allow-popups
Cross-Origin-Opener-Policy: unsafe-nonesame-origin provides the strongest isolation. same-origin-allow-popups is needed if your site uses OAuth flows or third-party popups.
Pairing COOP with Cross-Origin-Embedder-Policy: require-corp (COEP) enables SharedArrayBuffer and high-resolution timers, which are needed for certain web performance and WebAssembly use cases.
How to Add Security Headers
Nginx
server {
# ... your existing config ...
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Frame-Options "SAMEORIGIN" 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 Cross-Origin-Opener-Policy "same-origin-allow-popups" 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''; frame-ancestors ''self''" always;
}The always keyword ensures headers are added to all responses, including 4xx and 5xx error responses.
Apache
<IfModule mod_headers.c>
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
Header always set X-Frame-Options "SAMEORIGIN"
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=()"
Header always set Cross-Origin-Opener-Policy "same-origin-allow-popups"
Header always set Content-Security-Policy "default-src ''self''; script-src ''self''; style-src ''self'' ''unsafe-inline''; img-src ''self'' data: https:"
</IfModule>Node.js / Express
The easiest approach is the helmet middleware, which sets sensible defaults for all security headers:
import helmet from ''helmet'';
import express from ''express'';
const app = express();
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["''self''"],
scriptSrc: ["''self''"],
styleSrc: ["''self''", "''unsafe-inline''"],
imgSrc: ["''self''", "data:", "https:"],
fontSrc: ["''self''"],
frameAncestors: ["''self''"],
},
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true,
},
referrerPolicy: { policy: ''strict-origin-when-cross-origin'' },
permittedCrossDomainPolicies: false,
}));Next.js
Add headers in next.config.js:
/** @type {import(''next'').NextConfig} */
const nextConfig = {
async headers() {
return [
{
source: ''/(.*)'',
headers: [
{ key: ''X-Frame-Options'', value: ''SAMEORIGIN'' },
{ key: ''X-Content-Type-Options'', value: ''nosniff'' },
{ key: ''Referrer-Policy'', value: ''strict-origin-when-cross-origin'' },
{ key: ''Permissions-Policy'', value: ''camera=(), microphone=(), geolocation=()'' },
{ key: ''Strict-Transport-Security'', value: ''max-age=31536000; includeSubDomains; preload'' },
{
key: ''Content-Security-Policy'',
value: "default-src ''self''; script-src ''self'' ''unsafe-eval'' ''unsafe-inline''; style-src ''self'' ''unsafe-inline''; img-src ''self'' data: https:;"
},
],
},
];
},
};
module.exports = nextConfig;Note: Next.js requires ''unsafe-eval'' and ''unsafe-inline'' in CSP for development mode. Use environment-specific CSP values in production.
How to Test Your Headers
After deploying, verify your changes:
Quick terminal check:
curl -s -I https://yourdomain.com | grep -i "security\\|content-security\\|strict-transport\\|x-frame\\|x-content\\|referrer\\|permissions\\|cross-origin"Full automated scan: ZeriFlow checks all headers simultaneously, validates their values (not just their presence), and gives you a score with specific remediation guidance for anything that is missing or misconfigured.
Common Mistakes to Avoid
Setting CSP too loosely: Content-Security-Policy: default-src * is essentially the same as having no CSP at all. Make sure your policy actually restricts something.
Forgetting the `always` flag in Nginx: Without always, headers are only added to 200 responses. Error pages and redirects will be missing them.
Setting HSTS before your site is fully on HTTPS: If any page or subdomain is still HTTP, HSTS will break access to it for visitors who have already received the header.
Using X-Frame-Options ALLOWFROM: This was never properly implemented across browsers. Use CSP frame-ancestors for per-origin framing control.
Deploying CSP in enforcement mode without testing: CSP violations will silently break resources without any user-visible error in some cases, or cause hard failures in others. Always start with Content-Security-Policy-Report-Only.
Quick-Reference: Security Headers Checklist
| Header | Minimum Recommended Value | Breaks Anything? |
|---|---|---|
Strict-Transport-Security | max-age=31536000; includeSubDomains | Only if site is not fully HTTPS |
X-Frame-Options | SAMEORIGIN | No |
X-Content-Type-Options | nosniff | No |
Referrer-Policy | strict-origin-when-cross-origin | Rarely (some third-party analytics) |
Permissions-Policy | camera=(), microphone=(), geolocation=() | No |
Cross-Origin-Opener-Policy | same-origin-allow-popups | Only if you use third-party popups |
Content-Security-Policy | Start with report-only | Yes if not carefully configured |
Security headers are the lowest-effort, highest-impact security improvement most sites can make. Start with the ones that carry no risk — X-Content-Type-Options, X-Frame-Options, Referrer-Policy, and Permissions-Policy — and work toward a complete CSP once those are in place.
If you want to see exactly which headers your site is missing right now, run a free scan at ZeriFlow.
Check all your security headers instantly — free.
See exactly which headers are missing and how to fix them.