Skip to content
WebScore LogoWebScore
security14 min read

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.

January 9, 2026
security headersCSPHSTSX-Frame-OptionsXSS preventionclickjackingCORSweb security

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 HTTPS

Strict 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 year
  • includeSubDomains - Apply to all subdomains
  • preload - 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 referrer
  • same-origin - Send only on same origin
  • strict-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/AR

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

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