caduh

Security Headers Cheat Sheet — copy‑paste defaults that actually help

6 min read

Practical, modern defaults for CSP, HSTS, Referrer-Policy, Permissions-Policy, COOP/COEP/CORP, X-Content-Type-Options, X-Frame-Options vs frame-ancestors, cookies, and cache controls—with NGINX/Apache/Express snippets.

TL;DR

  • Turn on HSTS, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, and a strict CSP (start in report-only).
  • Prefer frame-ancestors in CSP over X-Frame-Options.
  • Use COOP/COEP/CORP when you need cross‑origin isolation (e.g., SharedArrayBuffer).
  • Mark cookies Secure; HttpOnly; SameSite=Lax (or None with Secure for 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-CT is obsolete; X-XSS-Protection is 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-dynamic lets 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. Keep X-Frame-Options: SAMEORIGIN as legacy backup.
  • Public CDNs/analytics: consider moving third‑party scripts behind your own subdomain with Subresource Integrity and nonces.
  • File downloads: set Content-Disposition: attachment and X-Content-Type-Options: nosniff on untrusted content.
  • OAuth callbacks: SameSite=None; Secure often 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)

  1. Add Report‑Only CSP + Reporting‑Endpoints; monitor violations for 1–2 weeks.
  2. Fix inline scripts/styles by adding nonces (or hashes) and removing unsafe-inline.
  3. Turn on HSTS (no preload yet).
  4. Add Permissions‑Policy, Referrer‑Policy, nosniff, COOP/COEP/CORP (if you need isolation).
  5. When violations go to near‑zero, enforce CSP and consider HSTS preload.
  6. 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

  1. Drop in the helmet config (or server headers) above.
  2. Generate per‑request nonces and move inline code to nonced blocks.
  3. Turn on Report‑Only CSP + Reporting‑Endpoints and fix violations.
  4. Enforce CSP, add HSTS preload, and ship CI checks (curl -I + grep).
  5. Re‑audit quarterly; keep a central policy file shared across apps/services.