HTTP Security Headers: The Complete Guide

security http headers web security xss web development
HTTP Security Headers: The Complete Guide

Your website might have HTTPS, but that’s just the start. HTTP security headers tell browsers how to handle your content securely—blocking attacks that encryption alone can’t prevent.

These headers cost nothing to implement but protect against real threats: cross-site scripting (XSS), clickjacking, MIME-type sniffing, and more.

Why Security Headers Matter

Without security headers, browsers make permissive assumptions. They’ll:

  • Execute inline scripts (XSS vector)
  • Embed your pages in iframes (clickjacking)
  • Guess content types (MIME sniffing attacks)
  • Allow insecure upgrades (protocol downgrade attacks)

Security headers change these defaults to safer options.

Essential Security Headers

Content-Security-Policy (CSP)

The most powerful security header. CSP controls which resources can load on your page.

Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:;

This policy:

  • default-src 'self' - Only load resources from your domain
  • script-src - Allow scripts from your domain and inline scripts
  • style-src - Allow styles from your domain and inline styles
  • img-src - Allow images from your domain, data URIs, and any HTTPS source

CSP Directives

DirectiveControls
default-srcFallback for all resource types
script-srcJavaScript sources
style-srcCSS sources
img-srcImage sources
font-srcWeb font sources
connect-srcXHR, WebSocket, fetch destinations
frame-srciframe sources
frame-ancestorsWho can embed your page

Common CSP Values

  • 'self' - Same origin only
  • 'none' - Block all
  • 'unsafe-inline' - Allow inline scripts/styles (weakens protection)
  • 'unsafe-eval' - Allow eval() (avoid if possible)
  • https: - Any HTTPS source
  • data: - Data URIs
  • nonce-xyz - Specific inline scripts with matching nonce

X-Frame-Options

Prevents clickjacking by controlling iframe embedding:

X-Frame-Options: DENY

Options:

  • DENY - Never allow framing
  • SAMEORIGIN - Only allow same-origin framing
  • ALLOW-FROM uri - Specific origin (deprecated, use CSP instead)

CSP’s frame-ancestors directive is more flexible, but X-Frame-Options provides fallback for older browsers.

X-Content-Type-Options

Prevents MIME type sniffing:

X-Content-Type-Options: nosniff

Without this, browsers might interpret a text file as JavaScript if an attacker can inject content. Always use nosniff.

Strict-Transport-Security (HSTS)

Forces HTTPS connections:

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
  • max-age - How long to remember (seconds)
  • includeSubDomains - Apply to all subdomains
  • preload - Submit to browser preload lists (permanent!)

Warning: Only enable preload if you’re certain your entire domain can be HTTPS-only forever.

Referrer-Policy

Controls how much referrer information to send:

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

Common values:

  • no-referrer - Never send referrer
  • same-origin - Only for same-origin requests
  • strict-origin-when-cross-origin - Full path for same-origin, origin only for cross-origin HTTPS, nothing for HTTP
  • origin - Only send the origin (domain), not the full path

Permissions-Policy (formerly Feature-Policy)

Controls which browser features your page can use:

Permissions-Policy: camera=(), microphone=(), geolocation=()

This blocks camera, microphone, and geolocation—even if your JavaScript or embedded content tries to request them.

Here’s a solid baseline for most websites:

Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-ancestors 'none';
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Strict-Transport-Security: max-age=31536000; includeSubDomains
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()

Adjust based on your needs:

  • Using Google Analytics? Add it to connect-src and script-src
  • Embedding YouTube? Add frame-src https://www.youtube.com
  • Using Google Fonts? Add to font-src and style-src

Implementation by Server

Nginx

add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self';" always;

Apache

Header always set X-Frame-Options "DENY"
Header always set X-Content-Type-Options "nosniff"
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Content-Security-Policy "default-src 'self';"

Vercel (vercel.json)

{
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        { "key": "X-Frame-Options", "value": "DENY" },
        { "key": "X-Content-Type-Options", "value": "nosniff" },
        { "key": "Strict-Transport-Security", "value": "max-age=31536000; includeSubDomains" }
      ]
    }
  ]
}

Netlify (_headers file)

/*
  X-Frame-Options: DENY
  X-Content-Type-Options: nosniff
  Strict-Transport-Security: max-age=31536000; includeSubDomains
  Referrer-Policy: strict-origin-when-cross-origin

Testing Your Headers

Before deploying, test your configuration. Common issues:

  • CSP too restrictive: Blocks legitimate resources, breaking functionality
  • CSP too permissive: 'unsafe-inline' and 'unsafe-eval' weaken protection
  • Missing headers: Easy to forget one
  • Conflicting headers: Multiple definitions can cause unexpected behavior

Our Security Headers Checker analyzes any URL and grades your implementation:

  • Checks for all recommended headers
  • Validates CSP syntax and security
  • Identifies missing headers
  • Explains each header’s purpose

It’s free and provides specific recommendations for improvement.

Debugging CSP Issues

CSP is the most complex header and the most likely to cause problems.

Report-Only Mode

Test without breaking your site:

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

This logs violations without blocking resources. Review the reports, then switch to enforcing mode.

Browser Console

CSP violations appear in the browser console:

Refused to load the script 'https://example.com/script.js'
because it violates the following Content Security Policy directive...

Add the blocked source to your policy (if legitimate) or investigate why it’s loading.

Security Headers and Third-Party Scripts

Modern websites often include third-party scripts: analytics, chat widgets, payment processors, ads. Each requires CSP exceptions.

The tradeoff:

  • Strict CSP: Maximum security, but may break third-party integrations
  • Permissive CSP: Works with everything, but less protection

Common approach:

  1. Start with a strict policy
  2. Add specific exceptions for each third-party
  3. Avoid wildcards when possible
script-src 'self' https://www.google-analytics.com https://js.stripe.com;

What About Legacy Headers?

Some headers are deprecated or unnecessary:

  • X-XSS-Protection: Deprecated, can be removed. CSP is the modern solution.
  • X-Powered-By: Remove it—it just reveals your tech stack to attackers
  • Expect-CT: Deprecated since Certificate Transparency is now required

Take Action

  1. Run your site through our Security Headers Checker
  2. Implement the missing headers (start with X-Frame-Options and X-Content-Type-Options—they’re low risk)
  3. Add HSTS once you’re confident your site works over HTTPS
  4. Implement CSP in report-only mode first

If you need help with security configuration or a comprehensive security audit, get in touch.