TL;DR
- Turn on HSTS, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, and a strict CSP (start in report-only).
- Prefer
frame-ancestorsin CSP overX-Frame-Options. - Use COOP/COEP/CORP when you need cross‑origin isolation (e.g., SharedArrayBuffer).
- Mark cookies
Secure; HttpOnly; SameSite=Lax(orNonewithSecurefor cross‑site). - Never cache sensitive pages:
Cache-Control: no-store. - Roll out with Report‑Only + reporting endpoint, monitor, then enforce.
1) The essentials (copy‑paste)
| Header | What it guards | Good default | Notes |
|---|---|---|---|
| Strict-Transport-Security | Downgrade/mitm to HTTP | max-age=15552000; includeSubDomains | After HTTPS is solid, add preload and submit to HSTS preload list. |
| Content-Security-Policy | XSS, clickjacking, injection | See policy below | Start with Report‑Only; use nonces or hashes; avoid unsafe-inline. |
| X-Content-Type-Options | MIME sniffing | nosniff | Prevents browsers from guessing content types. |
| Referrer-Policy | Leaky referers | strict-origin-when-cross-origin | Sensible balance of privacy & analytics. |
| Permissions-Policy | Browser capabilities | e.g., geolocation=(), camera=(), microphone=() | Formerly Feature‑Policy; opt‑out by default. |
| Cross-Origin-Opener-Policy | XS-Leaks, isolation | same-origin | Use with COEP to enable cross‑origin isolation. |
| Cross-Origin-Embedder-Policy | Isolation for embeds | require-corp | Needs cooperation from resources (CORS or CORP). |
| Cross-Origin-Resource-Policy | Who may embed your resources | same-site (or same-origin) | Add on static assets/APIs to block cross‑site loads. |
| X-Frame-Options | Clickjacking (legacy) | DENY | Prefer CSP: frame-ancestors 'none'; keep XFO for older UAs. |
| Origin-Agent-Cluster | Process isolation | ?1 | Helps isolate origins, reduces XS‑leak surface. |
| Cache-Control | Sensitive pages | no-store (for auth pages) | Use sensible caching elsewhere. |
| Set-Cookie | Cookie safety | Secure; HttpOnly; SameSite=Lax | Use SameSite=None; Secure only when truly cross‑site. |
Deprecated/legacy:
Expect-CTis obsolete;X-XSS-Protectionis removed/ignored by modern browsers. Focus on CSP + good coding practices.
2) CSP (Content-Security-Policy) you can actually ship
Strict baseline (nonce‑based)
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-<RANDOM>' 'strict-dynamic';
style-src 'self' 'nonce-<RANDOM>';
img-src 'self' data: https:;
font-src 'self' https: data:;
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
base-uri 'self';
object-src 'none';
upgrade-insecure-requests;
frame-src https://trusted-embed.example;
report-to csp-endpoint;
- Generate a fresh nonce per response; add it to inline
<script nonce="...">/<style nonce="...">. strict-dynamiclets trusted nonced scripts load others without whitelisting every host.- If you rely on third‑party scripts/styles, prefer hashes over wildcards; if you must use CDNs, pin to subresource integrity (SRI).
Start in Report‑Only
Content-Security-Policy-Report-Only: ...; report-to=csp-endpoint
Reporting-Endpoints: csp-endpoint="https://reports.example.com/csp"
Some older UAs only support
report-uri; you can include it as a fallback.
3) Cookies (session + CSRF)
Set-Cookie: session=...; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=...- Cross‑site (OIDC callbacks, embedded widgets) need
SameSite=None; Secure. - For CSRF, use SameSite + double submit token or ORIGIN/REFERER checks on state‑changing requests.
4) Cross‑origin isolation (when you need it)
To use SharedArrayBuffer and certain APIs safely:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Then ensure all embedded resources opt in via either CORS (Access-Control-Allow-Origin) or CORP:
Cross-Origin-Resource-Policy: same-site # or same-origin on your assets
Third‑party scripts must permit being embedded under COEP or they will be blocked.
5) NGINX, Apache, and Express snippets
NGINX
# HSTS (enable after HTTPS is verified everywhere)
add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always;
# Core protections
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), camera=(), microphone=(), usb=(), payment=()" 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-site" always;
add_header Origin-Agent-Cluster "?1" always;
# CSP (nonce placeholder: set with sub_filter or templating in your app)
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'nonce-__NONCE__' 'strict-dynamic'; style-src 'self' 'nonce-__NONCE__'; img-src 'self' data: https:; font-src 'self' https: data:; connect-src 'self' https://api.example.com; frame-ancestors 'none'; base-uri 'self'; object-src 'none'; upgrade-insecure-requests; report-to csp-endpoint" always;
# Reporting API endpoint
add_header Reporting-Endpoints 'csp-endpoint="https://reports.example.com/csp"' always;
Apache (httpd.conf)
Header always set Strict-Transport-Security "max-age=15552000; includeSubDomains"
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "geolocation=(), camera=(), microphone=()"
Header always set Cross-Origin-Opener-Policy "same-origin"
Header always set Cross-Origin-Embedder-Policy "require-corp"
Header always set Cross-Origin-Resource-Policy "same-site"
Header always set Origin-Agent-Cluster "?1"
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'nonce-__NONCE__' 'strict-dynamic'; style-src 'self' 'nonce-__NONCE__'; img-src 'self' data: https:; font-src 'self' https: data:; connect-src 'self' https://api.example.com; frame-ancestors 'none'; base-uri 'self'; object-src 'none'; upgrade-insecure-requests"
Header always set Reporting-Endpoints "csp-endpoint="https://reports.example.com/csp""
Express (Node)
import helmet from "helmet";
import crypto from "node:crypto";
app.use(helmet({
hsts: { maxAge: 15552000, includeSubDomains: true }, // add preload after testing
referrerPolicy: { policy: "strict-origin-when-cross-origin" },
crossOriginOpenerPolicy: { policy: "same-origin" },
crossOriginEmbedderPolicy: { policy: "require-corp" },
crossOriginResourcePolicy: { policy: "same-site" },
originAgentCluster: true,
// X-Content-Type-Options nosniff is on by default
}));
app.use((req, res, next) => {
const nonce = crypto.randomBytes(16).toString("base64");
res.locals.nonce = nonce;
res.setHeader("Content-Security-Policy",
`default-src 'self'; ` +
`script-src 'self' 'nonce-${nonce}' 'strict-dynamic'; ` +
`style-src 'self' 'nonce-${nonce}'; ` +
`img-src 'self' data: https:; ` +
`font-src 'self' https: data:; ` +
`connect-src 'self' https://api.example.com; ` +
`frame-ancestors 'none'; base-uri 'self'; object-src 'none'; upgrade-insecure-requests; ` +
`report-to csp-endpoint`
);
res.setHeader("Reporting-Endpoints", `csp-endpoint="https://reports.example.com/csp"`);
next();
});
6) Special cases
- Apps that must be iframed (e.g., admin inside another site): replace
frame-ancestors 'none'with the specific origins. KeepX-Frame-Options: SAMEORIGINas legacy backup. - Public CDNs/analytics: consider moving third‑party scripts behind your own subdomain with Subresource Integrity and nonces.
- File downloads: set
Content-Disposition: attachmentandX-Content-Type-Options: nosniffon untrusted content. - OAuth callbacks:
SameSite=None; Secureoften required; validate origin/host strictly. - APIs (JSON): add
X-Content-Type-Options: nosniff,Cross-Origin-Resource-Policy: same-site, and configure CORS explicitly.
7) Rollout plan (low risk → enforce)
- Add Report‑Only CSP + Reporting‑Endpoints; monitor violations for 1–2 weeks.
- Fix inline scripts/styles by adding nonces (or hashes) and removing
unsafe-inline. - Turn on HSTS (no preload yet).
- Add Permissions‑Policy, Referrer‑Policy, nosniff, COOP/COEP/CORP (if you need isolation).
- When violations go to near‑zero, enforce CSP and consider HSTS preload.
- Set cookie flags; disable caching for auth pages. Document exceptions.
8) Quick diagnostics
- Chrome DevTools → Security: verifies HTTPS/HSTS.
- Network tab: view Response Headers; check CSP violations in Console.
- curl -I https://example.com: sanity check headers.
- Scan with Mozilla Observatory and SecurityHeaders.com (treat suggestions with judgment).
9) Pitfalls & fast fixes
| Pitfall | Why it hurts | Fix |
|---|---|---|
| unsafe-inline scripts/styles | XSS bypass | Switch to nonces/hashes |
| Allowing * in connect-src | Data exfil possible | Whitelist exact APIs; prefer same‑origin |
| Over‑broad frame-src | Clickjacking risk | Use frame-ancestors and limit to trusted origins |
| HSTS on HTTP or before HTTPS is ready | Lockouts | Serve HSTS only on HTTPS after validation |
| SameSite=None without Secure | Cookie rejection | Always pair with Secure |
| COEP without CORS/CORP on assets | Breakage | Ensure all resources opt‑in to be embeddable |
| Only CSP, no coding hygiene | Still XSS | Output-encode, avoid dangerous sinks, add input validation |
Quick checklist
- [ ] HSTS (then preload) and X-Content-Type-Options: nosniff.
- [ ] CSP with nonces/hashes,
frame-ancestors,object-src 'none'. - [ ] Referrer-Policy: strict-origin-when-cross-origin.
- [ ] Permissions-Policy: disable sensitive features by default.
- [ ] COOP/COEP/CORP if you need isolation.
- [ ] Cookies: Secure; HttpOnly; SameSite=….
- [ ] no-store on auth/account pages.
- [ ] Report‑Only → monitor → enforce.
One‑minute adoption plan
- Drop in the helmet config (or server headers) above.
- Generate per‑request nonces and move inline code to nonced blocks.
- Turn on Report‑Only CSP + Reporting‑Endpoints and fix violations.
- Enforce CSP, add HSTS preload, and ship CI checks (
curl -I+ grep). - Re‑audit quarterly; keep a central policy file shared across apps/services.