Complete Guide to HTTP Security Headers: CSP, HSTS, and Modern Browser Security 2026
Master HTTP security headers in 2026: Content-Security-Policy configuration, HSTS preloading, Permissions-Policy, COEP/COOP for cross-origin isolation, and real-world configs for nginx, Apache, and Next.js.
HTTP security headers are the fastest ROI in web security. A few config lines can eliminate entire vulnerability classes β XSS, clickjacking, MIME sniffing, cross-origin attacks β before your application code even runs.
This guide covers every header that matters in 2026, with real production configs for nginx, Apache, and Next.js, plus the browser devtools workflow for validating your implementation. Use our free HTTP Security Scanner to audit your headers without writing a line of code.
Why Headers Matter More Than Ever
Modern browsers enforce security policies at the network layer, before JavaScript executes. A strong header set:
- Blocks inline script injection even if your app has an XSS bug
- Prevents clickjacking without JavaScript
- Stops protocol downgrade attacks at the browser level
- Enables cross-origin isolation required for
SharedArrayBufferand high-resolution timers
The cost is configuration time. The benefit is defense-in-depth you canβt get from application code alone.
Header 1: Content-Security-Policy (CSP)
CSP is the most powerful and most complex security header. It tells the browser exactly which resources are allowed to load β by origin, type, and source.
Basic CSP Syntax
Content-Security-Policy: directive-name source-list; directive-name source-list
A minimal, restrictive policy:
Content-Security-Policy:
default-src 'self';
script-src 'self' https://cdn.jsdelivr.net;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://api.yourservice.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self'
Key Directives Explained
| Directive | Controls | Recommended Value |
|---|---|---|
default-src | Fallback for all resources | 'self' |
script-src | JavaScript sources | 'self' + explicit CDNs, never 'unsafe-eval' |
style-src | CSS sources | 'self'; add 'unsafe-inline' only if needed |
img-src | Image sources | 'self' data: https: |
connect-src | XHR/fetch/WebSocket | 'self' + your API origins |
frame-ancestors | Who can iframe you | 'none' replaces X-Frame-Options |
base-uri | <base> tag URLs | 'self' |
form-action | Where forms can POST | 'self' |
The Nonce Approach (Recommended over unsafe-inline)
Instead of 'unsafe-inline' for scripts, use a nonce:
<!-- Server generates a fresh nonce per request -->
<script nonce="abc123xyz">
// Your inline script
</script>
Content-Security-Policy: script-src 'nonce-abc123xyz' 'strict-dynamic'
'strict-dynamic' propagates trust to scripts loaded by the nonced script, eliminating the need to allowlist every CDN URL.
Next.js CSP with Nonces
// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import crypto from "crypto";
export function middleware(request: NextRequest) {
const nonce = crypto.randomBytes(16).toString("base64");
const csp = [
`default-src 'self'`,
`script-src 'self' 'nonce-${nonce}' 'strict-dynamic'`,
`style-src 'self' 'unsafe-inline'`,
`img-src 'self' data: https:`,
`connect-src 'self'`,
`frame-ancestors 'none'`,
`base-uri 'self'`
].join("; ");
const response = NextResponse.next();
response.headers.set("Content-Security-Policy", csp);
response.headers.set("x-nonce", nonce); // pass to page component
return response;
}
In your root layout:
// app/layout.tsx
import { headers } from "next/headers";
export default async function RootLayout({ children }) {
const nonce = (await headers()).get("x-nonce") ?? "";
return (
<html>
<head>
<script nonce={nonce} src="/analytics.js" />
</head>
<body>{children}</body>
</html>
);
}
CSP Report-Only Mode
Never deploy a strict CSP without testing it first. Use Content-Security-Policy-Report-Only to collect violations without blocking anything:
Content-Security-Policy-Report-Only:
default-src 'self';
script-src 'self';
report-uri /csp-violations
Set up a simple violation endpoint:
// app/api/csp-violations/route.ts
export async function POST(req: Request) {
const report = await req.json();
console.warn("CSP violation:", report["csp-report"]);
// Ship to your logging service
return new Response(null, { status: 204 });
}
Run in report-only mode for 2 weeks, fix violations, then enforce.
Header 2: HTTP Strict Transport Security (HSTS)
HSTS tells browsers to never connect to your site over HTTP β even if the user types http://. This prevents SSL stripping attacks.
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
| Parameter | What It Does |
|---|---|
max-age=31536000 | Cache the HSTS policy for 1 year |
includeSubDomains | Apply to all subdomains |
preload | Request inclusion in browser preload lists |
Warning: includeSubDomains breaks HTTP-only subdomains permanently until max-age expires. Audit all your subdomains first.
HSTS Preloading
Preloading bakes your domain into Chrome, Firefox, and Safariβs HSTS lists β so the browser never even attempts HTTP, eliminating the first-request vulnerability.
Submit at hstspreload.org. Requirements:
- Valid HTTPS certificate
- HTTPS redirect for all HTTP traffic
max-ageβ₯ 31536000includeSubDomainspresentpreloadpresent
Header 3: X-Frame-Options (Deprecated β Use CSP frame-ancestors)
X-Frame-Options: DENY
Still useful for older browsers, but frame-ancestors in CSP supersedes it:
Content-Security-Policy: frame-ancestors 'none'
frame-ancestors 'none' prevents all framing. frame-ancestors 'self' allows same-origin frames only. frame-ancestors https://trusted.com allows a specific origin.
Header 4: X-Content-Type-Options
Prevents MIME-sniffing β browsers guessing content type from content instead of the Content-Type header. This can lead to script execution from files served as text/plain.
X-Content-Type-Options: nosniff
One value, always set it.
Header 5: Permissions-Policy
Controls access to browser APIs: camera, microphone, geolocation, payment, etc. Replaces the deprecated Feature-Policy header.
Permissions-Policy:
camera=(),
microphone=(),
geolocation=(),
payment=(self),
usb=(),
fullscreen=(self)
Empty () denies the feature entirely. (self) allows only same-origin access. (self "https://trusted.com") allows same-origin plus a specific third party.
This header is critical when embedding third-party iframes β it prevents them from accessing sensitive APIs even if theyβre compromised.
Header 6: Cross-Origin Headers (COEP, COOP, CORP)
Cross-origin isolation is required to use SharedArrayBuffer and high-resolution timing β both needed for WebAssembly threading and performance profiling.
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Resource-Policy: same-origin
| Header | What It Does |
|---|---|
COEP require-corp | Blocks cross-origin resources that donβt opt in |
COOP same-origin | Isolates your browsing context from cross-origin windows |
CORP same-origin | Restricts who can load this resource |
Warning: COEP breaks loading cross-origin images/scripts that donβt set Cross-Origin-Resource-Policy: cross-origin. Audit all third-party dependencies before enabling.
For resources you control that need cross-origin loading (CDN assets):
Cross-Origin-Resource-Policy: cross-origin
Header 7: Referrer-Policy
Controls how much URL information is sent in the Referer header when users navigate away.
Referrer-Policy: strict-origin-when-cross-origin
| Policy | Behavior |
|---|---|
no-referrer | Never send Referer |
strict-origin | Send only origin (no path/query), HTTPS β HTTP drops entirely |
strict-origin-when-cross-origin | Full URL for same-origin, origin only for cross-origin (recommended) |
unsafe-url | Always send full URL β never use this |
Production Configurations
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=(self)" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'" always;
# Include in your server block:
# include /etc/nginx/conf.d/security-headers.conf;
Test your nginx config: nginx -t
Apache
# .htaccess or VirtualHost
<IfModule mod_headers.c>
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=()"
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'"
</IfModule>
Vercel (next.config.ts)
// next.config.ts
import type { NextConfig } from "next";
const securityHeaders = [
{ 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=(self)"
},
{
key: "Strict-Transport-Security",
value: "max-age=63072000; includeSubDomains; preload"
}
// Note: CSP with nonces is handled in middleware.ts
];
const config: NextConfig = {
async headers() {
return [
{
source: "/(.*)",
headers: securityHeaders
}
];
}
};
export default config;
Cloudflare Workers
// worker.ts
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const response = await fetch(request);
const newResponse = new Response(response.body, response);
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(
"Strict-Transport-Security",
"max-age=31536000; includeSubDomains; preload"
);
newResponse.headers.set(
"Permissions-Policy",
"camera=(), microphone=(), geolocation=()"
);
return newResponse;
}
};
Validation Workflow
Browser DevTools
- Open DevTools β Network tab
- Click any request β Headers
- Check Response Headers for each security header
- Open Console β look for CSP violation warnings
Command Line
# Quick header dump
curl -sI https://yoursite.com | grep -i "security\|csp\|hsts\|content-type\|permissions\|referrer\|frame"
# Verbose with redirect following
curl -sIL https://yoursite.com
Automated Scanning
Run securityheaders.com or our HTTP Security Scanner to get an instant grade and remediation checklist. Aim for A+.
Priority Rollout Order
If youβre adding headers to an existing production site, this order minimizes risk:
X-Content-Type-Options: nosniffβ no side effects, add immediatelyReferrer-Policyβ safe, may affect analytics (check your tracking)X-Frame-Options: DENYβ safe unless you embed yourself in iframesHSTSβ start with shortmax-age(300s), extend after confirming HTTPS worksPermissions-Policyβ audit which APIs your app uses firstCSPβ deploy inReport-Onlymode first, enforce after 2 weeks of zero violationsCOEP/COOP/CORPβ last, only if you need cross-origin isolation
Security Headers Score Card
| Header | Priority | Complexity | Impact |
|---|---|---|---|
| HSTS | Critical | Low | High |
| X-Content-Type-Options | High | None | Medium |
| X-Frame-Options / CSP frame-ancestors | High | Low | High |
| Referrer-Policy | Medium | Low | Medium |
| CSP | Critical | High | Very High |
| Permissions-Policy | Medium | Medium | Medium |
| COEP/COOP | Low (unless WASM) | High | Medium |
Further Reading
- Zero Trust Architecture for Web Developers β defense-in-depth beyond headers
- API Security Best Practices β secure your backend APIs
- CSP Checker Tool β validate your CSP policy syntax instantly
50 CLI Commands That Save Hours Every Week
The exact Git shortcuts, file search tricks, Docker commands, and productivity hacks that senior developers use daily. Compiled from 5+ years of shipping production code.
- Git mastery (10 cmds)
- File & search wizardry
- Docker & DevOps tricks
- Process & network tools
- Productivity boosters
- macOS & Linux install guide
No spam. 1 email/week max. Unsubscribe anytime.
Found this guide useful? Check out our free developer tools.
Affiliate disclosure: Some links below are affiliate links β we may earn a small commission at no extra cost to you. Learn more.
Recommended Tools & Resources
DigitalOcean
$200 credit for new users. Simple, affordable cloud hosting for developers.
GitHub Student Pack
Free access to 100+ developer tools. Perfect for students and new devs.
Vercel
Deploy frontend apps instantly. Free tier is generous for side projects.
DevPlaybook Products
Boilerplates, scripts & AI toolkits to 10x your dev workflow.