Website Security Headers: Complete Implementation Guide 2026
Secure your website with CSP, HSTS, X-Frame-Options, and modern security headers. Prevent XSS, clickjacking, and MITM attacks with comprehensive configurations.
Security headers are your website's first line of defense against common attacks. Properly configured headers can prevent XSS, clickjacking, MITM attacks, and data breaches. This guide covers all essential security headers with practical implementation examples.
Why Security Headers Matter
The Threat Landscape:
- XSS Attacks: 40% of all web attacks (2025 data)
- Clickjacking: Affects 1 in 3 websites
- Data Breaches: Cost average $4.45M per incident
- MITM Attacks: 25% increase in SSL stripping attempts
1. Content Security Policy (CSP)
CSP prevents XSS attacks by controlling which resources can load on your page.
Basic CSP
# Nginx
add_header 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'; connect-src 'self' https://api.example.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self';" always;# Apache (.htaccess)
Header always set 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'; connect-src 'self' https://api.example.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self';"CSP Directives Explained
Content-Security-Policy:
default-src 'self'; # Default policy for all resources
script-src 'self' https://cdn.jsdelivr.net; # JavaScript sources
style-src 'self' 'unsafe-inline'; # CSS sources
img-src 'self' data: https:; # Image sources
font-src 'self'; # Font sources
connect-src 'self' https://api.example.com; # XHR, WebSocket, Fetch
media-src 'self'; # Audio/video sources
object-src 'none'; # Plugins (Flash, etc.)
frame-src 'none'; # Iframes
frame-ancestors 'none'; # Can't be embedded
base-uri 'self'; # <base> tag restrictions
form-action 'self'; # Form submission URLs
upgrade-insecure-requests; # Upgrade HTTP to HTTPSStrict CSP with Nonces
<!-- Generate random nonce per request -->
<?php
$nonce = base64_encode(random_bytes(16));
header("Content-Security-Policy: script-src 'nonce-$nonce' 'strict-dynamic'");
?>
<!-- Use nonce in script tags -->
<script nonce="<?php echo $nonce; ?>">
// Your inline script
console.log('This script is allowed')
</script>
<script nonce="<?php echo $nonce; ?>" src="https://cdn.example.com/app.js"></script>Next.js implementation:
// next.config.js
const crypto = require('crypto')
module.exports = {
async headers() {
return [
{
source: '/:path*',
headers: [
{
key: 'Content-Security-Policy',
value: `
default-src 'self';
script-src 'self' 'unsafe-eval' 'unsafe-inline';
style-src 'self' 'unsafe-inline';
img-src 'self' data: blob: https:;
font-src 'self' data:;
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
`.replace(/\s{2,}/g, ' ').trim()
}
]
}
]
}
}CSP Reporting
# Report violations to endpoint
add_header Content-Security-Policy "default-src 'self'; report-uri /csp-report; report-to csp-endpoint;" always;
# Report-To header for modern browsers
add_header Report-To '{"group":"csp-endpoint","max_age":10886400,"endpoints":[{"url":"/csp-report"}]}' always;CSP report endpoint (Node.js):
// Express endpoint to receive CSP reports
app.post('/csp-report', express.json({ type: 'application/csp-report' }), (req, res) => {
const report = req.body
console.log('CSP Violation:', {
blockedURI: report['blocked-uri'],
violatedDirective: report['violated-directive'],
originalPolicy: report['original-policy'],
sourceFile: report['source-file'],
lineNumber: report['line-number']
})
// Log to monitoring service
logToMonitoring('csp-violation', report)
res.status(204).end()
})2. HTTP Strict Transport Security (HSTS)
Forces browsers to use HTTPS only.
# Nginx
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;# Apache
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"HSTS Directives:
max-age=31536000- Remember for 1 yearincludeSubDomains- Apply to all subdomainspreload- Include in browser preload lists
Submit to HSTS Preload List:
# Requirements:
# 1. Serve valid certificate
# 2. Redirect HTTP to HTTPS (same host)
# 3. Serve HSTS header on base domain
# 4. max-age >= 31536000 (1 year)
# 5. includeSubDomains directive
# 6. preload directive
# Submit at: https://hstspreload.org/3. X-Frame-Options
Prevents clickjacking attacks.
# Nginx - Prevent embedding in iframes
add_header X-Frame-Options "DENY" always;
# Or allow same origin
add_header X-Frame-Options "SAMEORIGIN" always;
# Or allow specific domain
add_header X-Frame-Options "ALLOW-FROM https://trusted-site.com" always;# Apache
Header always set X-Frame-Options "DENY"Modern alternative using CSP:
# frame-ancestors is more flexible than X-Frame-Options
add_header Content-Security-Policy "frame-ancestors 'none';" always;
# Allow specific origins
add_header Content-Security-Policy "frame-ancestors 'self' https://trusted-site.com;" always;4. X-Content-Type-Options
Prevents MIME type sniffing.
# Nginx
add_header X-Content-Type-Options "nosniff" always;# Apache
Header always set X-Content-Type-Options "nosniff"Why it matters:
<!-- Without nosniff, browser might execute this as JavaScript -->
<script src="image.jpg"></script>
<!-- With nosniff, browser respects Content-Type and won't execute -->5. Referrer-Policy
Controls how much referrer information is sent.
# Nginx - Recommended policy
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# No referrer at all
add_header Referrer-Policy "no-referrer" always;
# Same origin only
add_header Referrer-Policy "same-origin" always;<!-- HTML meta tag -->
<meta name="referrer" content="strict-origin-when-cross-origin">
<!-- Per-link policy -->
<a href="https://external-site.com" referrerpolicy="no-referrer">External Link</a>Policy options:
no-referrer- Never send referrersame-origin- Send only on same originstrict-origin- Send origin only (HTTPS→HTTP: nothing)strict-origin-when-cross-origin- Recommended
6. Permissions-Policy
Controls browser features and APIs.
# Nginx - Restrict powerful features
add_header Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=(), usb=(), magnetometer=(), gyroscope=(), speaker=(self)" always;
# Allow specific features for your site
add_header Permissions-Policy "geolocation=(self), microphone=(self), camera=(self)" always;Available directives:
Permissions-Policy:
accelerometer=(), # Motion sensor
ambient-light-sensor=(), # Light sensor
autoplay=(self), # Video autoplay
camera=(), # Camera access
encrypted-media=(self), # DRM
fullscreen=(self), # Fullscreen API
geolocation=(), # Location
gyroscope=(), # Gyroscope
magnetometer=(), # Magnetometer
microphone=(), # Microphone
midi=(), # MIDI devices
payment=(), # Payment Request API
picture-in-picture=(self), # PiP
usb=(), # USB devices
xr-spatial-tracking=() # VR/AR7. Cross-Origin Policies
CORS (Cross-Origin Resource Sharing)
# Nginx - CORS headers
location /api/ {
# Allow specific origin
add_header Access-Control-Allow-Origin "https://app.example.com" always;
# Allow credentials
add_header Access-Control-Allow-Credentials "true" always;
# Allow specific methods
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
# Allow specific headers
add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-Requested-With" always;
# Cache preflight requests for 1 hour
add_header Access-Control-Max-Age "3600" always;
# Handle OPTIONS requests
if ($request_method = OPTIONS) {
return 204;
}
}Express.js CORS:
const cors = require('cors')
// Simple CORS
app.use(cors({
origin: 'https://app.example.com',
credentials: true
}))
// Advanced CORS configuration
const corsOptions = {
origin: function (origin, callback) {
const allowedOrigins = [
'https://app.example.com',
'https://admin.example.com'
]
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true)
} else {
callback(new Error('Not allowed by CORS'))
}
},
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
exposedHeaders: ['X-Total-Count'],
maxAge: 3600
}
app.use(cors(corsOptions))Cross-Origin-Embedder-Policy (COEP)
# Nginx
add_header Cross-Origin-Embedder-Policy "require-corp" always;Cross-Origin-Opener-Policy (COOP)
# Nginx
add_header Cross-Origin-Opener-Policy "same-origin" always;Cross-Origin-Resource-Policy (CORP)
# Nginx
add_header Cross-Origin-Resource-Policy "same-origin" always;8. Complete Security Headers Configuration
Nginx (Complete)
# /etc/nginx/conf.d/security-headers.conf
# Security Headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Content Security Policy
add_header Content-Security-Policy "
default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.jsdelivr.net;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
img-src 'self' data: https:;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://api.example.com;
frame-ancestors 'self';
base-uri 'self';
form-action 'self';
upgrade-insecure-requests;
" always;
# Permissions Policy
add_header Permissions-Policy "
geolocation=(self),
microphone=(),
camera=(),
payment=(),
usb=(),
magnetometer=(),
gyroscope=(),
speaker=(self)
" always;
# Cross-Origin Policies
add_header Cross-Origin-Embedder-Policy "require-corp" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Cross-Origin-Resource-Policy "same-origin" always;
# Remove server version
server_tokens off;
# Custom server header
more_clear_headers Server;
add_header Server "Secure Server" always;Apache (Complete)
# .htaccess
<IfModule mod_headers.c>
# HSTS
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
# XSS Protection
Header always set X-XSS-Protection "1; mode=block"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
# CSP
Header always set 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'; connect-src 'self'; frame-ancestors 'self'; base-uri 'self'; form-action 'self';"
# Permissions
Header always set Permissions-Policy "geolocation=(self), microphone=(), camera=()"
# Remove server signature
Header unset Server
Header always set Server "Secure Server"
</IfModule>
# Disable server signature
ServerSignature Off
ServerTokens ProdNext.js (Complete)
// next.config.js
module.exports = {
async headers() {
return [
{
source: '/:path*',
headers: [
{
key: 'Strict-Transport-Security',
value: 'max-age=31536000; includeSubDomains; preload'
},
{
key: 'X-Frame-Options',
value: 'SAMEORIGIN'
},
{
key: 'X-Content-Type-Options',
value: 'nosniff'
},
{
key: 'Referrer-Policy',
value: 'strict-origin-when-cross-origin'
},
{
key: 'Permissions-Policy',
value: 'geolocation=(self), microphone=(), camera=()'
},
{
key: 'Content-Security-Policy',
value: `
default-src 'self';
script-src 'self' 'unsafe-eval' 'unsafe-inline';
style-src 'self' 'unsafe-inline';
img-src 'self' data: blob: https:;
font-src 'self' data:;
connect-src 'self';
frame-ancestors 'self';
`.replace(/\s{2,}/g, ' ').trim()
}
]
}
]
}
}9. Testing Security Headers
Online Tools
# Security Headers scanner
curl -I https://yoursite.com | grep -i "security"
# Or use online tools:
# https://securityheaders.com/
# https://observatory.mozilla.org/Automated Testing
// Jest test for security headers
describe('Security Headers', () => {
test('should have HSTS header', async () => {
const response = await fetch('https://yoursite.com')
const hsts = response.headers.get('strict-transport-security')
expect(hsts).toContain('max-age=31536000')
expect(hsts).toContain('includeSubDomains')
})
test('should have CSP header', async () => {
const response = await fetch('https://yoursite.com')
const csp = response.headers.get('content-security-policy')
expect(csp).toBeTruthy()
expect(csp).toContain("default-src 'self'")
})
test('should have X-Frame-Options', async () => {
const response = await fetch('https://yoursite.com')
const xfo = response.headers.get('x-frame-options')
expect(xfo).toMatch(/DENY|SAMEORIGIN/)
})
})Security Headers Checklist
Essential Headers
- ✅ Strict-Transport-Security (HSTS)
- ✅ Content-Security-Policy (CSP)
- ✅ X-Frame-Options (or frame-ancestors)
- ✅ X-Content-Type-Options
- ✅ Referrer-Policy
- ✅ Permissions-Policy
Additional Security
- ✅ Remove server version info
- ✅ Configure CORS properly
- ✅ Set Cross-Origin policies
- ✅ Implement CSP reporting
- ✅ Test with security scanners
- ✅ Monitor CSP violations
Advanced
- ✅ Use CSP nonces for inline scripts
- ✅ Submit to HSTS preload list
- ✅ Implement Subresource Integrity (SRI)
- ✅ Use Certificate Transparency
- ✅ Enable Expect-CT header
Conclusion
Security headers are easy to implement and provide immediate protection against common attacks. They're your first line of defense and should be considered mandatory for any production website.
Key Takeaways:
- CSP: Prevents XSS and controls resource loading
- HSTS: Enforces HTTPS connections
- X-Frame-Options: Prevents clickjacking
- CORS: Secure cross-origin requests
- Testing: Use automated tools to verify configuration
Start with HSTS and X-Frame-Options for quick wins, then implement CSP and fine-tune based on your application's needs.
Need security header analysis? WebScore.now provides comprehensive security audits with specific configuration recommendations.
Related Articles
Scan Your Website Now
Get a comprehensive analysis of your website's performance, SEO, security, and more.