HTTP Security Headers (HSTS, CSP, etc.)

intermediate http headers security hsts csp web

HTTP security headers are response headers we set on a web server to tell browsers “behave safely on this site.” They’re free, take a few lines of nginx config, and stop a surprising number of attacks.

In simple language: by default browsers are pretty lax — they’ll let any script run, render any iframe, follow any redirect. These headers lock things down.

HSTS — Strict-Transport-Security

Tells the browser “always use HTTPS for this domain — even if the user types http://.”

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
  • max-age — how long the browser remembers (in seconds). 1 year is standard.
  • includeSubDomains — applies to *.example.com too.
  • preload — opts into the HSTS preload list baked into browsers, so even the very first visit is HTTPS-only.

Stops: SSL stripping attacks (where a MITM downgrades HTTPS to HTTP).

CSP — Content-Security-Policy

A whitelist for what content the page is allowed to load. Probably the most powerful and the most annoying header to configure.

Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; frame-ancestors 'none'
  • default-src 'self' — by default, only load resources from our own origin.
  • script-src — JS sources allowed (no inline scripts unless 'unsafe-inline' or a nonce).
  • frame-ancestors — who can iframe us (replaces X-Frame-Options).

Stops: XSS (rogue inline scripts), clickjacking, data exfiltration, mixed content.

Tip: roll out CSP in report-only mode first to find what breaks.

Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report

X-Content-Type-Options

X-Content-Type-Options: nosniff

Tells the browser “trust the Content-Type header — don’t try to guess.” Without this, IE/older browsers might treat a .txt upload as an HTML or JS file, enabling XSS through MIME sniffing. Always set this.

X-Frame-Options

X-Frame-Options: DENY

Or SAMEORIGIN. Tells browsers “don’t let other sites iframe me.” Stops clickjacking (where an attacker iframes our site and tricks users into clicking buttons they can’t see).

Modern alternative: Content-Security-Policy: frame-ancestors 'none'. Set both for legacy browser support.

Referrer-Policy

Referrer-Policy: strict-origin-when-cross-origin

Controls how much info the Referer header leaks when our users click external links. Common values:

  • no-referrer — never send Referer at all.
  • same-origin — only send for same-site requests.
  • strict-origin-when-cross-origin — send full URL same-origin, only origin cross-origin, nothing on HTTPS-to-HTTP. Sensible default.

Stops: leaking session tokens or user data via URL parameters in Referer.

Permissions-Policy

(Used to be called Feature-Policy.) Controls which browser APIs the page can use.

Permissions-Policy: camera=(), microphone=(), geolocation=(self), payment=()
  • camera=() — disable camera entirely.
  • geolocation=(self) — only our own page can request location.

Stops: rogue scripts (or compromised third parties) from accessing sensitive APIs.

Other Useful Headers

  • Cross-Origin-Opener-Policy: same-origin — isolates browsing context, mitigates Spectre.
  • Cross-Origin-Embedder-Policy: require-corp — required for cross-origin isolation features (SharedArrayBuffer).
  • Cross-Origin-Resource-Policy: same-origin — controls who can fetch our resources.
  • X-XSS-Protection: 0 — old IE/Edge XSS filter, disable it (it caused more bugs than it fixed; CSP replaces it).

A Sane Starting Set

For a typical web app, these six headers cover the common cases:

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Content-Security-Policy: default-src 'self'
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()

Set them once in nginx / Caddy / your CDN, scan with securityheaders.com, iterate.

Example: Setting Headers in Nginx

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" 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 Content-Security-Policy "default-src 'self'" always;

The always keyword ensures the header is set even on error responses (4xx, 5xx).

Interview Tip

When asked “how do you secure a web app?” — these headers are an easy, concrete answer alongside HTTPS, input validation, and auth. Knowing what each one prevents (CSP → XSS, X-Frame-Options → clickjacking, HSTS → SSL stripping) shows depth.