HTTP Security Headers: Your Website's First Line of Defense
HTTP security headers are instructions your web server sends to browsers, telling them how to behave when handling your site's content. They're one of the easiest and most effective ways to protect your users from common web attacks.
Why Security Headers Matter
Without security headers, browsers make default assumptions that may not be safe. Attackers exploit these defaults to steal data, hijack sessions, and trick users. Security headers tell browsers exactly how to protect your users.
- Site can be embedded in malicious iframes
- Browsers may interpret files incorrectly
- Connections can be downgraded to HTTP
- Scripts from any source can execute
- Clickjacking attacks blocked
- MIME-type confusion prevented
- HTTPS enforced automatically
- Only approved scripts can run
The Seven Headers at a Glance
Here's the full set you'll configure below, what each defends, and how urgent it is to ship:
HSTS core
Forces HTTPS on every connection — blocks SSL stripping and downgrade attacks.
CSP powerful
Controls which scripts, styles and resources may load — the strongest XSS defence.
X-Frame-Options easy
Stops your pages being embedded in hostile iframes — blocks clickjacking.
X-Content-Type-Options easy
Stops MIME sniffing so disguised files can't be executed as scripts.
Referrer-Policy privacy
Controls how much URL data leaks in the Referer header to other sites.
Permissions-Policy privacy
Restricts camera, mic, geolocation and other powerful features — for you and embeds.
COOP / COEP / CORP advanced
Isolate your page from Spectre-style cross-origin leaks and unlock high-precision APIs.
1. Strict-Transport-Security (HSTS)
What it does: Forces browsers to always use HTTPS, even if the user types "http://" or clicks an HTTP link. Once set, the browser will automatically convert all HTTP requests to HTTPS.
Real-World Attack It Prevents
Example Configuration
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
What Each Part Means
max-age=31536000- Remember this setting for 1 year (31,536,000 seconds)includeSubDomains- Apply to all subdomains too (api.example.com, www.example.com)preload- Allow inclusion in browser preload lists (ultimate protection)
2. Content-Security-Policy (CSP)
What it does: Controls which resources (scripts, styles, images, fonts) can load on your page. It's the most powerful header for preventing XSS attacks, but also the most complex to configure.
Real-World Attack It Prevents
<script src="https://evil.com/steal-cookies.js"></script>
Without CSP, the browser loads and executes the malicious script. With CSP, the browser blocks it
because evil.com isn't on your approved list.
Example Configuration
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' https://fonts.gstatic.com
What Each Directive Means
default-src 'self'- By default, only load resources from your own domainscript-src 'self' https://cdn.example.com- Scripts only from your domain and your CDNstyle-src 'self' 'unsafe-inline'- Styles from your domain, allow inline stylesimg-src 'self' data: https:- Images from your domain, data URIs, or any HTTPS sourcefont-src 'self' https://fonts.gstatic.com- Fonts from your domain and Google Fonts
Content-Security-Policy-Report-Only first
to see what would be blocked without actually breaking your site. Fix issues, then switch to enforcing mode.
3. X-Frame-Options
What it does: Controls whether your site can be embedded in iframes on other sites. This prevents clickjacking attacks where your site is invisibly overlaid on a malicious page.
Real-World Attack It Prevents
Example Configuration
X-Frame-Options: DENY
Available Options
DENY- Never allow framing (most secure)SAMEORIGIN- Only allow framing by pages on the same domainALLOW-FROM https://trusted.com- Only allow framing by a specific domain (deprecated)
frame-ancestors directive provides more
flexibility: frame-ancestors 'self' https://partner.com
4. X-Content-Type-Options
What it does: Prevents browsers from "sniffing" the content type of files. This stops attacks where malicious files are disguised as harmless ones.
Real-World Attack It Prevents
Example Configuration
X-Content-Type-Options: nosniff
There's only one value: nosniff. This tells browsers: "Trust the Content-Type header
I send. Don't try to guess."
5. Referrer-Policy
What it does: Controls what information is sent in the Referer header when users click links or load resources from your site. This protects user privacy and prevents data leakage.
Real-World Privacy Issue It Prevents
/user/12345/medical-records.
When a user clicks an external link, by default the full URL is sent to that site - revealing
user IDs and sensitive page information. With proper Referrer-Policy, you control exactly what's shared.
Example Configuration
Referrer-Policy: strict-origin-when-cross-origin
Common Policies Explained
no-referrer- Never send referrer information (most private)same-origin- Only send referrer for same-origin requestsstrict-origin- Send only the origin (not full URL) for HTTPS, nothing for HTTPstrict-origin-when-cross-origin- Full URL for same-origin, just origin for cross-origin (recommended)
6. Permissions-Policy
What it does: Controls which browser features (camera, microphone, geolocation, etc.) can be used on your site and by embedded content. This limits the damage if your site is compromised.
Real-World Risk It Mitigates
Example Configuration
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(self)
What This Example Does
camera=()- No one can access the camera, not even your own sitemicrophone=()- Microphone access completely disabledgeolocation=()- Location tracking disabledpayment=(self)- Only your own site can use the Payment Request API
7. Cross-Origin Isolation (COOP / COEP / CORP)
What it does: These three headers work together to isolate your page from
cross-origin attacks like Spectre side-channel leaks, and unlock high-precision APIs
(SharedArrayBuffer, performance.now() at microsecond resolution,
Atomics.wait) that browsers gate behind cross-origin isolation.
Real-World Risk They Mitigate
Example Configuration
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Resource-Policy: same-origin
What Each Does
- COOP (
Cross-Origin-Opener-Policy): Severs the window reference between your page and cross-origin popups/openers.same-originis the recommended value;same-origin-allow-popupsis a useful relaxation if you use OAuth popups. - COEP (
Cross-Origin-Embedder-Policy): Refuses to load cross-origin subresources unless they explicitly opt in via CORP or CORS. Userequire-corp(strict) orcredentialless(relaxed — sends no credentials so third-party resources can load without CORP). - CORP (
Cross-Origin-Resource-Policy): Goes on your subresources (images, scripts, JSON) to tell other origins whether they can embed them.same-origin,same-site, orcross-origin.
Cross-Origin-Embedder-Policy-Report-Only first,
review the reports, then enforce. Only enable if you actually need a cross-origin-isolated context
(e.g. for WebAssembly threads, video editing, multiplayer games).
Secure vs Insecure Configuration
The same header can help or hurt depending on its value. A few patterns to follow — and a few to avoid:
- HSTS with a long
max-ageplusincludeSubDomains, only after every subdomain serves HTTPS. - CSP with a tight
default-src 'self'and an explicit allow-list per resource type. - Roll CSP and COEP out in
Report-Onlymode first, then enforce. - Send headers with
always(nginx) so they apply to error responses too.
- Enabling
preloadHSTS before subdomains are HTTPS-ready — you lock yourself out. - A CSP with
script-src 'unsafe-inline'or wildcard*— it defeats the point. - Relying on the deprecated
X-Frame-Options: ALLOW-FROMinstead offrame-ancestors. - Shipping
X-Content-Type-Optionsany value other thannosniff.
Quick Implementation Guide
Apache (.htaccess)
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Header always set X-Frame-Options "DENY"
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
Header always set Content-Security-Policy "default-src 'self'; script-src 'self'"
Nginx
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'" always;
Express.js (Node.js)
const helmet = require('helmet');
app.use(helmet()); // Adds sensible defaults for all headers
Testing Your Headers
After implementing security headers, it's crucial to verify they're working correctly. Use our free tool to scan your site and get a detailed report.
Summary: Header Cheat Sheet
| Header | Protects Against | Recommended Value |
|---|---|---|
Strict-Transport-Security |
SSL stripping, downgrade attacks | max-age=31536000; includeSubDomains |
Content-Security-Policy |
XSS, code injection | default-src 'self' (customize per site) |
X-Frame-Options |
Clickjacking | DENY |
X-Content-Type-Options |
MIME sniffing attacks | nosniff |
Referrer-Policy |
URL data leakage | strict-origin-when-cross-origin |
Permissions-Policy |
Feature abuse | camera=(), microphone=(), geolocation=() |