HTTP Cheatsheet
Security Headers
Use this HTTP reference while you build software engineering projects, review code, or refresh the syntax you reach for most.
The full set, and what each one stops
| Header | Stops | Recommended value |
|---|---|---|
Strict-Transport-Security | protocol downgrade, cookie theft on plaintext | max-age=63072000; includeSubDomains; preload |
Content-Security-Policy | XSS, injected scripts, data exfiltration | see below, nonce based |
X-Content-Type-Options | MIME sniffing turning an upload into script | nosniff |
X-Frame-Options | clickjacking (legacy mechanism) | DENY, superseded by CSP frame-ancestors |
Referrer-Policy | URL leakage to third parties | strict-origin-when-cross-origin |
Permissions-Policy | unwanted camera, mic, geolocation access | camera=(), microphone=(), geolocation=() |
Cross-Origin-Opener-Policy | cross-window attacks, window.opener abuse | same-origin |
Cross-Origin-Embedder-Policy | Spectre-class cross-origin leaks | require-corp |
Cross-Origin-Resource-Policy | your resources embedded elsewhere | same-origin |
X-XSS-Protection | nothing, the auditor is removed | omit it, or 0 |
X-Powered-By, Server | version fingerprinting | remove or make them generic |
X-XSS-Protection is worth calling out. The browser feature is gone, the header
does nothing useful, and its legacy filter mode introduced vulnerabilities of its
own. Do not add it.
HSTS
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload| Directive | Effect |
|---|---|
max-age=<s> | how long the browser refuses plaintext for this host |
includeSubDomains | applies to every subdomain as well |
preload | opts into the browser-shipped preload list |
Rules that bite:
- HSTS is ignored over plain HTTP, so it must be sent on the HTTPS response.
- Roll out with a short max-age (600), verify, then raise it. A long max-age
on a host that later needs HTTP is very hard to undo, since only visiting
browsers can be told to forget it.
- preload is close to permanent. Every subdomain must serve valid HTTPS
forever, including internal ones, before you submit.
- max-age=0 is how you retract it, and only for clients that visit again.
Content-Security-Policy
The highest-value and highest-effort header. Each directive whitelists sources for one kind of resource.
| Directive | Controls |
|---|---|
default-src | the fallback for most fetch directives |
script-src | JavaScript, the one that actually stops XSS |
style-src | stylesheets |
img-src | images |
connect-src | fetch, XHR, WebSocket, EventSource, beacons |
font-src | web fonts |
media-src | audio and video |
frame-src | what this page may embed |
frame-ancestors | who may embed this page, replaces X-Frame-Options |
form-action | where forms may submit |
base-uri | restricts <base>, which can otherwise re-root every relative URL |
object-src | plugins, always set to 'none' |
worker-src | workers and shared workers |
upgrade-insecure-requests | rewrites http subresource URLs to https |
report-uri / report-to | where violation reports are posted |
| Source expression | Meaning |
|---|---|
'self' | the document's own origin |
'none' | nothing at all |
'unsafe-inline' | inline scripts and styles, defeats most of CSP |
'unsafe-eval' | eval and new Function |
'nonce-<random>' | only tags carrying this exact per-response nonce |
'sha256-<hash>' | only inline content matching this hash |
'strict-dynamic' | trust scripts loaded by already-trusted scripts, ignore host lists |
https: | any HTTPS origin |
https://cdn.example.com | one host |
A strict, practical policy:
Content-Security-Policy: default-src 'self'; script-src 'nonce-r4nd0mPerRequest' 'strict-dynamic' https: 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self'; font-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'; form-action 'self'; upgrade-insecure-requests
<script nonce="r4nd0mPerRequest">/* allowed by the nonce */</script>
The 'unsafe-inline' and https: entries alongside a nonce are deliberate
fallbacks for old browsers, which ignore 'strict-dynamic' and nonces.
Modern browsers see the nonce and ignore both. The nonce must be
cryptographically random and regenerated per response, since a static nonce is
no better than 'unsafe-inline'.
Deploy in report-only mode first, collect violations, then enforce:
Content-Security-Policy-Report-Only: default-src 'self'; report-to csp-endpoint Reporting-Endpoints: csp-endpoint="https://example.com/_csp"
Framing, referrers, and permissions
X-Frame-Options: DENY
Content-Security-Policy: frame-ancestors 'none'X-Frame-Options takes DENY or SAMEORIGIN only. The old
ALLOW-FROM was never widely supported, so use CSP frame-ancestors when you
need an allowlist, and send both headers for older browsers.
Referrer-Policy | Sends |
|---|---|
no-referrer | nothing, ever |
same-origin | full URL to your own origin, nothing cross-origin |
strict-origin | origin only, and nothing on an HTTPS to HTTP downgrade |
strict-origin-when-cross-origin | full URL same-origin, origin only cross-origin. The default and the right answer |
no-referrer-when-downgrade | the old default, weaker |
unsafe-url | full URL always, leaks paths and query strings |
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=(), fullscreen=(self)
() denies a feature everywhere, (self) allows it on your own origin, and
(self "https://embed.example.com") extends it to a named origin. Denying what
you do not use costs nothing and removes whole permission-prompt attack surfaces.
Cross-origin isolation
The COOP and COEP pair are what unlock SharedArrayBuffer and
high-resolution timers, by proving no cross-origin content shares your process.
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Resource-Policy: same-origin| Header | Effect |
|---|---|
COOP: same-origin | severs window.opener between your page and cross-origin windows |
COEP: require-corp | every cross-origin subresource must opt in with CORP or CORS |
CORP: same-origin | other sites cannot embed this resource at all |
Turning on COEP: require-corp will break third-party embeds that do not
send Cross-Origin-Resource-Policy: cross-origin, so audit before enabling. Check
self.crossOriginIsolated in the console to confirm it took effect.
Auditing what you actually serve
curl -sI https://example.com | grep -iE \ 'strict-transport|content-security|x-content-type|x-frame|referrer-policy|\ permissions-policy|cross-origin|server|x-powered-by' # see everything, in order, following redirects curl -sSL -D - -o /dev/null https://example.com # confirm the redirect chain reaches HTTPS before HSTS is expected to apply curl -sSIL http://example.com | grep -iE '^HTTP|^location|strict-transport'
| Finding | Fix |
|---|---|
| No HSTS on the HTTPS response | add it, short max-age first |
CSP contains 'unsafe-inline' in script-src with no nonce | XSS is not mitigated, move to nonces |
No nosniff | an uploaded file can be interpreted as script |
Referrer-Policy missing | modern browsers default sensibly, but set it explicitly |
Server: nginx/1.18.0 | drop the version, server_tokens off |
| Headers present on HTML but not on API responses | set them at the edge, for every route |
Two closing points. These headers are defense in depth, not a substitute for output encoding, parameterized queries, and server-side authorization. And a header only protects the responses that carry it, so set them centrally at the proxy or framework layer rather than per route, where one omission becomes the hole.