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.comtoo.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.