Understanding HTTP Security Headers: Complete Guide
Learn about essential HTTP security headers that protect your website from attacks. Complete implementation guide with examples.
Introduction
HTTP security headers are a fundamental layer of web security. They instruct browsers on how to behave when handling your website's content, protecting against various attack vectors. This guide covers all essential security headers and how to implement them.
Why Security Headers Matter
Security headers protect against:
- Cross-site scripting (XSS)
- Clickjacking
- MIME type sniffing
- Man-in-the-middle attacks
- Information leakage
- Cross-site request forgery (CSRF)
Essential Security Headers
1. Strict-Transport-Security (HSTS)
Purpose: Enforces HTTPS connections and prevents protocol downgrade attacks.
Header Syntax:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Directives:
| Directive | Purpose | | ----------------- | -------------------------------------------- | | max-age | Time (seconds) browser should remember HTTPS | | includeSubDomains | Apply HSTS to all subdomains | | preload | Include in browser HSTS preload list |
Implementation:
# nginx
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Apache
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
// Express.js
app.use(
helmet({
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true,
},
}),
);
Best Practice: Start with a short max-age (e.g., 300 seconds) and increase once confirmed HTTPS works properly.
2. Content-Security-Policy (CSP)
Purpose: Restricts sources for various content types, preventing XSS attacks.
Header Syntax:
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' https://trusted.cdn.com; style-src 'self' 'unsafe-inline'
Directives:
| Directive | Controls | | --------------- | -------------------------------- | | default-src | Default for all resource types | | script-src | JavaScript sources | | style-src | CSS sources | | img-src | Image sources | | font-src | Font sources | | connect-src | Fetch, XMLHttpRequest, WebSocket | | media-src | Audio and video | | object-src | Plugins (Flash, etc.) | | frame-src | Frame sources | | base-uri | Base URL | | form-action | Form target URLs | | frame-ancestors | Parents that may embed this page | | report-uri | CSP violation reporting endpoint |
Example Policies:
Strict (high security):
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data: https:; object-src 'none'; base-uri 'self'; form-action 'self'
Moderate (allows CDN):
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.trusted.com; style-src 'self' 'unsafe-inline' https://cdn.trusted.com; img-src 'self' data: https:;
Development Mode:
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-violation-report-endpoint
Implementation:
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';" always;
Nonce-based CSP (more secure):
<!-- Server generates unique nonce -->
<script nonce="{random_nonce}">
// Inline JavaScript
</script>
Content-Security-Policy: script-src 'self' 'nonce-{random_nonce}'
3. X-Frame-Options
Purpose: Prevents clickjacking by controlling who can embed your site.
Header Syntax:
X-Frame-Options: DENY
Values:
| Value | Meaning | | -------------- | -------------------------------------- | | DENY | No one can embed this page | | SAMEORIGIN | Only same origin can embed | | ALLOW-FROM uri | Specific origin can embed (deprecated) |
Note: CSP frame-ancestors is the modern replacement.
Implementation:
add_header X-Frame-Options "DENY" always;
CSP Equivalent:
Content-Security-Policy: frame-ancestors 'none';
4. X-Content-Type-Options
Purpose: Prevents MIME type sniffing, ensuring browsers respect the declared content type.
Header Syntax:
X-Content-Type-Options: nosniff
Implementation:
add_header X-Content-Type-Options "nosniff" always;
5. Referrer-Policy
Purpose: Controls how much referrer information is sent with navigations.
Header Syntax:
Referrer-Policy: strict-origin-when-cross-origin
Values:
| Value | Behavior | | ------------------------------- | ------------------------------------------------------------------ | | no-referrer | No referrer sent | | no-referrer-when-downgrade | Full referrer for same-origin, none for HTTPS to HTTP | | origin | Only origin (no path) | | strict-origin | Origin only, not sent HTTPS to HTTP | | origin-when-cross-origin | Full URL for same-origin, origin for cross-origin | | strict-origin-when-cross-origin | Recommended - Full same-origin, origin for secure cross-origin | | unsafe-url | Full URL always (not recommended) |
Implementation:
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
6. Permissions-Policy (formerly Feature-Policy)
Purpose: Controls which browser features and APIs can be used.
Header Syntax:
Permissions-Policy: geolocation=(self), camera=(), microphone=(), payment=(self "https://payment.com")
Features to Control:
| Feature | Description | | --------------- | -------------------------------------- | | geolocation | Geolocation API | | camera | Camera access | | microphone | Microphone access | | payment | Payment Request API | | usb | USB devices | | magnetometer | Magnetometer API | | gyroscope | Gyroscope API | | fullscreen | Fullscreen API | | interest-cohort | FLoC (disable with interest-cohort=()) |
Implementation:
add_header Permissions-Policy "geolocation=(self), camera=(), microphone=(), payment=(self)" always;
7. Cross-Origin-Opener-Policy (COOP)
Purpose: Isolates your window from other windows, preventing cross-origin attacks.
Header Syntax:
Cross-Origin-Opener-Policy: same-origin
Values:
unsafe-none: No isolationsame-origin: Isolates from cross-origin windowssame-origin-allow-popups: Allows popups to maintain opener relationship
8. Cross-Origin-Resource-Policy (CORP)
Purpose: Prevents cross-origin reads of resources.
Header Syntax:
Cross-Origin-Resource-Policy: same-origin
Values:
same-site: Only same-site can readsame-origin: Only same-origin can readcross-origin: Any origin can read
9. Cross-Origin-Embedder-Policy (COEP)
Purpose: Requires CORP on cross-origin resources, enabling advanced features like SharedArrayBuffer.
Header Syntax:
Cross-Origin-Embedder-Policy: require-corp
Note: COEP, COOP, and CORP are often used together for advanced security isolation.
Security Headers Implementation
Complete nginx Configuration
server {
# HSTS
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# CSP
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none';" always;
# Frame protection
add_header X-Frame-Options "DENY" always;
# MIME sniffing
add_header X-Content-Type-Options "nosniff" always;
# Referrer policy
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Permissions policy
add_header Permissions-Policy "geolocation=(self), camera=(), microphone=(), payment=(self), usb=(), interest-cohort=()" always;
# Other security headers
add_header X-XSS-Protection "1; mode=block" always;
}
Complete Apache Configuration
# Enable headers module
LoadModule headers_module modules/mod_headers.so
<VirtualHost *:443>
ServerName example.com
# HSTS
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
# CSP
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; object-src 'none'"
# Frame protection
Header always set X-Frame-Options "DENY"
# MIME sniffing
Header always set X-Content-Type-Options "nosniff"
# Referrer policy
Header always set Referrer-Policy "strict-origin-when-cross-origin"
# Permissions policy
Header always set Permissions-Policy "geolocation=(self), camera=(), microphone=()"
# XSS protection (legacy)
Header always set X-XSS-Protection "1; mode=block"
</VirtualHost>
Express.js with Helmet
const express = require("express");
const helmet = require("helmet");
const app = express();
// Basic helmet (recommended defaults)
app.use(helmet());
// Custom configuration
app.use(
helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
objectSrc: ["'none'"],
baseUri: ["'self'"],
formAction: ["'self'"],
frameAncestors: ["'none'"],
},
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true,
},
referrerPolicy: { policy: "strict-origin-when-cross-origin" },
permissionsPolicy: {
features: {
geolocation: ["'self'"],
camera: ["'none'"],
microphone: ["'none'"],
},
},
}),
);
Next.js Configuration
// next.config.js
module.exports = {
async headers() {
return [
{
source: "/(.*)",
headers: [
{
key: "Strict-Transport-Security",
value: "max-age=31536000; includeSubDomains; preload",
},
{
key: "X-Frame-Options",
value: "DENY",
},
{
key: "X-Content-Type-Options",
value: "nosniff",
},
{
key: "Referrer-Policy",
value: "strict-origin-when-cross-origin",
},
{
key: "Permissions-Policy",
value: "geolocation=(self), camera=(), microphone=()",
},
{
key: "Content-Security-Policy",
value:
"default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none';",
},
],
},
];
},
};
Testing Security Headers
1. Browser DevTools
// Check security headers in console
fetch(window.location.href).then((response) => {
console.log("CSP:", response.headers.get("Content-Security-Policy"));
console.log("HSTS:", response.headers.get("Strict-Transport-Security"));
console.log("X-Frame-Options:", response.headers.get("X-Frame-Options"));
});
2. Online Tools
- Security Headers:
https://securityheaders.com/ - Mozilla Observatory:
https://observatory.mozilla.org/ - WebScope: Run our security scanner
3. Command Line
curl -I https://example.com
Common CSP Violations and Fixes
1. Inline Scripts Blocked
Error:
Refused to execute inline script because it violates the Content-Security-Policy
Fix: Use nonce or hash
<script nonce="random_value">
...
</script>
Content-Security-Policy: script-src 'self' 'nonce-random_value'
2. External Font Blocked
Fix: Add to CSP
Content-Security-Policy: font-src 'self' https://fonts.gstatic.com
3. eval() Blocked
Fix: Remove eval or add 'unsafe-eval' (not recommended)
Content-Security-Policy: script-src 'self' 'unsafe-eval'
Security Headers Checklist
Use this checklist to verify your security headers:
Essential
[ ] Strict-Transport-Security (max-age >= 31536000)
[ ] X-Frame-Options (DENY or SAMEORIGIN)
[ ] X-Content-Type-Options (nosniff)
[ ] Content-Security-Policy (default-src configured)
Recommended
[ ] Referrer-Policy (strict-origin-when-cross-origin)
[ ] Permissions-Policy (features restricted as needed)
[ ] Cross-Origin-Opener-Policy (same-origin)
Context-Dependent
[ ] Cross-Origin-Resource-Policy (for specific resources)
[ ] Cross-Origin-Embedder-Policy (when using SharedArrayBuffer)
Conclusion
Security headers are a critical defense layer for any website. Implement all essential headers first, then add context-specific ones based on your needs. Regular testing ensures your headers work as expected.
Run a security scan to check your website's security headers and get specific recommendations for improvement.