Help us fix this page
If you found a broken link, missing page, or incorrect redirect, please let us know. Your report helps us improve the website for everyone.

HTTP security headers are one of the most effective, low-effort security controls available to web developers. Set them once in your server configuration, and the browser enforces the policy on every subsequent request-blocking entire classes of attacks before your application code even runs.
This guide provides a technically accurate, implementation-focused reference for HTTP security headers, verified against current browser support and best practices as of August 2026.
default-src 'self', nonces for inline scripts, and frame-ancestors 'self'. Keep reporting enabled during and after rollout.report-to directive is now Baseline 2026. Since March 2026, report-to works across latest browsers. Support for the older Report-To header is declining; migrate to Reporting-Endpoints + report-to.X-XSS-Protection, and Expect-CT. These headers are deprecated or removed. Use HSTS, CSP, and rely on Certificate Transparency instead.X-Content-Type-Options: nosniff on every response. No exceptions. Low risk, material security improvement.Secure; HttpOnly; SameSite=Lax. Use SameSite=None; Secure only for third-party contexts.| Header | Attack Vector Mitigated |
|---|---|
| Content-Security-Policy (CSP) | XSS, data injection, clickjacking |
| HSTS | Protocol downgrade, SSL stripping |
| X-Content-Type-Options | MIME sniffing attacks |
| Referrer-Policy | Referrer data leakage |
| Permissions-Policy | Unauthorized browser feature access |
| COOP / COEP / CORP | Cross-origin data leaks, Spectre-type attacks |
| CORS | Unauthorized cross-origin API access |
| Fetch Metadata | CSRF and cross-site request abuse |
| Secure Cookies | Session hijacking, CSRF |
| Subresource Integrity | CDN asset tampering |
Content-Security-Policy-Report-Only and monitor violations before enforcing.curl checks and automated scanners in your pipeline.CSP is the most powerful security header. It restricts where your page can load scripts, styles, images, and other resources, mitigating XSS and data injection attacks.
Content-Security-Policy-Report-Only: default-src 'self'; base-uri 'self'; object-src 'none'; script-src 'self' 'nonce-{RANDOM}'; style-src 'self'; img-src 'self' data:; frame-ancestors 'self'; upgrade-insecure-requests; report-to csp-endpointContent-Security-Policy: default-src 'self'; base-uri 'self'; object-src 'none'; script-src 'self' 'nonce-{RANDOM}' 'strict-dynamic'; style-src 'self' 'nonce-{RANDOM}'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; form-action 'self'; upgrade-insecure-requestsThe report-to directive is now Baseline 2026-available across latest browsers since March 2026. Use Reporting-Endpoints to define endpoints:
Reporting-Endpoints: csp-endpoint="https://example.com/csp-reports"
Content-Security-Policy: ... report-to csp-endpointImportant: Browsers that support report-to ignore the older report-uri directive. Include both during transition, but plan to migrate fully to Reporting-Endpoints + report-to.
| Directive | Purpose | Recommendation |
|---|---|---|
default-src | Fallback for other fetch directives | 'self' |
script-src | Controls script execution | 'self' 'nonce-{RANDOM}' 'strict-dynamic' |
style-src | Controls stylesheet loading | 'self' 'nonce-{RANDOM}' |
frame-ancestors | Controls framing (clickjacking) | 'none' or 'self' |
object-src | Blocks plugins (Flash, etc.) | 'none' |
base-uri | Prevents base tag hijacking | 'self' |
upgrade-insecure-requests | Upgrades HTTP to HTTPS | Include |
| Name | Purpose | Critical Directives | Browser Support | Breakage Risk |
|---|---|---|---|---|
| CSP | Client-side content isolation | default-src, script-src with nonce, frame-ancestors | Chrome 25+, Firefox 23+, Safari 7+, Edge 12+ | Medium-High. Inline scripts without nonce break; third-party hosts must be declared |
HSTS forces browsers to use HTTPS for all future requests to your domain, preventing protocol downgrade and SSL stripping attacks.
Strict-Transport-Security: max-age=31536000; includeSubDomainsTo be eligible for the browser preload list, you need:
max-age ≥ 31,536,000 (one year)includeSubDomainspreload directiveWarning: The preload list is nearly irreversible. Removal takes several months and only affects future browser versions. Only submit when every subdomain serves HTTPS reliably and you have documented business approval.
As of April 2026, approximately 120,000 domains are in the Chrome preload list, and only 35.7% of sites that ship HSTS have enabled the preload directive.
| Name | Purpose | Critical Directives | Browser Support | Breakage Risk |
|---|---|---|---|---|
| HSTS | Transport security, HTTPS enforcement | max-age, includeSubDomains, preload | Chrome 4+, Firefox 4+, Safari 7+, Edge 12+ | High if any subdomain still serves HTTP |
Controls how much referrer information is sent in the Referer header.
Referrer-Policy: strict-origin-when-cross-originThis sends the full URL on same-origin requests, only the origin on cross-origin HTTPS-to-HTTPS, and nothing on HTTPS-to-HTTP downgrades.
For privacy-sensitive sites:
Referrer-Policy: no-referrerControls access to browser features like geolocation, camera, and microphone.
Note: Permissions-Policy is experimental and not yet Baseline. Check browser compatibility before deploying in production.
Permissions-Policy: geolocation=(), camera=(), microphone=()Prevents MIME sniffing-the browser’s dangerous habit of guessing content types.
X-Content-Type-Options: nosniffSet this on every response. No exceptions. Low risk, material security improvement.
These headers enable cross-origin isolation, protecting against cross-site leaks and enabling advanced features like SharedArrayBuffer.
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Resource-Policy: same-origin| Header | Browser Support |
|---|---|
| COOP | Chrome 83+, Firefox 79+, Safari 15.2+, Edge 83+ |
| COEP | Chrome 83+, Edge 83+, Opera 69+; Firefox and Safari support vary |
| CORP | Chrome 73+, Edge 79+, Firefox 74+ |
SharedArrayBuffer or performance APIs that require isolationCross-Origin-Resource-Policy. Add CORP: same-origin on your assets.credentialless.CORS governs which origins, methods, and headers may access protected resources.
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 600
Vary: OriginAccess-Control-Allow-Origin: https://example.com
Access-Control-Allow-Credentials: true
Vary: OriginAccess-Control-Allow-Origin: * with credentials. The spec forbids this, and browsers reject it.Vary: Origin when responses differ by origin.Access-Control-Allow-Headers: *.Access-Control-Max-Age modest (e.g., 600 seconds) so policy changes take effect quickly.Fetch Metadata headers (Sec-Fetch-Site, Sec-Fetch-Mode, Sec-Fetch-Dest, Sec-Fetch-User) tell the server about the request context. Use them to block cross-site abuse.
site = get_header("Sec-Fetch-Site")
mode = get_header("Sec-Fetch-Mode")
is_unsafe_method = (method in [POST, PUT, PATCH, DELETE])
is_cross_site = (site == "cross-site")
is_navigational = (mode == "navigate")
if (is_cross_site && is_unsafe_method && !is_navigational) {
reject()
}function fetchMetadataGuard(req, res, next) {
const site = req.get("Sec-Fetch-Site") || "";
const mode = req.get("Sec-Fetch-Mode") || "";
const dest = req.get("Sec-Fetch-Dest") || "";
const unsafe = ["POST", "PUT", "PATCH", "DELETE"].includes(req.method);
const allowedSameSite = site === "" || site === "same-origin" || site === "same-site";
const isNavigation = mode === "navigate" && dest === "document";
if (unsafe && !allowedSameSite && !isNavigation) return res.sendStatus(403);
next();
}| Header | Browser Support |
|---|---|
| Sec-Fetch-Site | Chrome 76+, Edge 79+, Opera 63+ |
| Sec-Fetch-Mode | Chrome 76+, Edge 79+ |
SRI ensures that CDN resources have not been tampered with by verifying a cryptographic hash.
<script src="https://cdn.example.com/app.js"
integrity="sha384-BASE64HASH"
crossorigin="anonymous"></script>crossorigin attribute. Browsers silently ignore SRI without it.openssl dgst -sha384 -binary app.js | openssl base64 -AProtect session cookies with the right flags.
Set-Cookie: session=abc...; Path=/; Secure; HttpOnly; SameSite=LaxPath=/ only when needed. Narrower paths reduce exposure.Domain to the fewest hosts possible.SameSite=None; Secure only for third-party cookies.| Header | Status | Reason | Replacement |
|---|---|---|---|
| HTTP Public-Key-Pins (HPKP) | Removed (2018) | High risk of self-inflicted lockout | HSTS preload + Certificate Transparency |
| X-XSS-Protection | Deprecated (2019) | Ineffective; can create XSS vulnerabilities | CSP with no inline scripts |
| Expect-CT | Deprecated (2022) | CT now enforced by browsers natively | Rely on Certificate Transparency |
| X-Frame-Options | Superseded | Limited syntax and control | CSP frame-ancestors |
# /etc/nginx/conf.d/security.conf
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), camera=(), microphone=()" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Cross-Origin-Embedder-Policy "require-corp" always;
add_header Cross-Origin-Resource-Policy "same-origin" always;
location / {
set_by_lua_block $csp_nonce {
local rand = require("resty.random").bytes(16, true)
return ngx.encode_base64(rand)
}
add_header Content-Security-Policy
"default-src 'self'; base-uri 'self'; object-src 'none'; \
script-src 'self' 'nonce-$csp_nonce'; style-src 'self'; img-src 'self' data:; \
frame-ancestors 'self'; upgrade-insecure-requests" always;
}# In <VirtualHost> or .htaccess
Header always set Content-Security-Policy "default-src 'self'; base-uri 'self'; object-src 'none'; script-src 'self' 'nonce-%{CSP_NONCE}e'; style-src 'self'; img-src 'self' data:; frame-ancestors 'self'; upgrade-insecure-requests"
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set X-Content-Type-Options "nosniff"import express from "express";
import crypto from "crypto";
const app = express();
app.use((req, res, next) => {
res.locals.cspNonce = crypto.randomBytes(16).toString("base64");
next();
});
app.use((req, res, next) => {
const nonce = res.locals.cspNonce;
res.setHeader("Content-Security-Policy",
"default-src 'self'; base-uri 'self'; object-src 'none'; " +
"script-src 'self' 'nonce-" + nonce + "'; style-src 'self'; img-src 'self' data:; " +
"frame-ancestors 'self'; upgrade-insecure-requests");
res.setHeader("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
res.setHeader("X-Content-Type-Options", "nosniff");
next();
});# Show all response headers
curl -s -D - https://example.com/ -o /dev/null
# Check HSTS
curl -s -D - https://example.com/ -o /dev/null | grep -i strict-transport-security
# Check CORS preflight
curl -s -D - -X OPTIONS https://api.example.com/endpoint \
-H "Origin: https://example.com" \
-H "Access-Control-Request-Method: POST" -o /dev/null| Phase | Timeline | Activities |
|---|---|---|
| Inventory & Baseline | Week 1 | Map all domains/subdomains; document current header state; identify all third-party dependencies |
| CSP Report-Only | Weeks 2-3 | Deploy CSP in report-only mode; monitor violations for 14+ days; fix legitimate violations |
| Basic Headers | Week 4 | Deploy X-Content-Type-Options: nosniff; Referrer-Policy; Permissions-Policy with safe defaults |
| HSTS Gradual | Weeks 5-8 | Start with max-age=300; gradually increase; add includeSubDomains only after all subdomains verified |
| CSP Enforcement | Week 9+ | Switch CSP to enforce mode; keep reporting enabled; monitor for new violations |
| Advanced Isolation | Optional | Deploy COOP/COEP/CORP only if needed; test cross-origin isolation requirements |
| Mistake | Fix |
|---|---|
Access-Control-Allow-Origin: * with credentials | Use an allowlist and Vary: Origin |
Only sending X-Frame-Options | Use frame-ancestors in CSP |
Forgetting X-Content-Type-Options: nosniff | Set it on every response |
| Not setting cookie flags | Use Secure; HttpOnly; SameSite |
| Preloading HSTS before ready | Only submit when every subdomain serves HTTPS |
CSP allows 'unsafe-inline' | Use nonces or hashes; remove inline handlers |
Missing frame-ancestors in CSP | Add it even if you also send X-Frame-Options |
COEP require-corp without CORP on assets | Add Cross-Origin-Resource-Policy on images, fonts, WASM |
report-to is Baseline-migrate from report-uri.X-Content-Type-Options: nosniff goes on every response. No exceptions.*. Always set Vary: Origin.curl, browser DevTools, securityheaders.com, and observatory.mozilla.org.HTTP security headers are one of the highest-leverage security controls available to web developers. A few lines of configuration can block XSS, clickjacking, protocol downgrade, and data leakage attacks-all before your application code runs.
Start with the basics: X-Content-Type-Options: nosniff, Referrer-Policy, and HSTS. Add CSP in Report-Only mode and monitor violations. Gradually tighten policies as you validate their impact. Test with curl and online scanners. Document your headers as configuration as code.
Security headers are not a silver bullet-they complement input validation, output encoding, and authentication. But they are a critical layer of defense that every production web application should have.
Need help securing your web applications? Playful Sparkle has been engineering digital products since 2004, offering Web Development, App Development, and UI/UX & Web Design services. Contact us to discuss how we can help harden your application security.