Security Headers Explained: The Complete Guide

HTTP security headers are one of the most overlooked defenses in web security. They cost nothing to implement, take minutes to configure, and protect your users against an entire class of attacks that firewalls and WAFs cannot stop. Yet the majority of websites on the internet deploy them incorrectly or not at all.

This guide covers every important security header, explains the specific attacks each one mitigates, and walks you through configuration for the most common server environments. By the end, you will know exactly how to achieve an A+ grade on a security headers audit.

How Security Headers Protect Your Users

When a browser receives a response from your server, it reads not just the HTML but a set of instructions embedded in the HTTP headers. These instructions tell the browser how to behave: whether to allow your site to be framed by another page, what resources it is permitted to load, whether to enforce HTTPS, and how much referrer information to pass along.

Without these headers, browsers fall back on permissive defaults designed for maximum compatibility, not maximum safety. An attacker who can inject a script into your page, trick a user into loading your site inside an iframe, or intercept an HTTP connection can exploit those defaults in ways that well-configured headers would completely prevent.

flowchart LR
    A[Browser Request] --> B[Your Server]
    B --> C[Response + Security Headers]
    C --> D{Browser Enforces Policy}
    D -->|CSP| E[Block unauthorized scripts]
    D -->|HSTS| F[Force HTTPS]
    D -->|X-Frame-Options| G[Prevent framing]
    D -->|Referrer-Policy| H[Limit data leakage]

Security headers are a browser-enforced policy layer. Unlike server-side validation, they protect users even when your application code has a vulnerability.


HTTP Strict Transport Security (HSTS)

The attack it prevents: SSL stripping, where an attacker intercepts an HTTP connection before your server can redirect it to HTTPS and serves the victim a downgraded HTTP version of your site.

HSTS tells browsers to never connect to your domain over plain HTTP, for a specified period of time. Once a browser has seen this header, it will internally redirect all HTTP requests to HTTPS before they ever leave the machine.

Configuration

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
  • max-age is the duration in seconds. One year (31536000) is the standard.
  • includeSubDomains extends the policy to all subdomains. Include this unless you have a subdomain that must serve HTTP.
  • preload signals that you want your domain included in the browser's built-in HSTS preload list. This protects users on their very first visit, before they have ever seen your header. Submit your domain at hstspreload.org after verifying your configuration is stable.

Common mistakes

Starting with a low max-age for testing is sensible, but many sites ship with max-age=0 or omit the header entirely in production. Do not add preload until you are certain your entire site and all subdomains can serve HTTPS permanently. Removal from the preload list is slow and cumbersome.

nginx:

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

Apache:

Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"

Next.js (next.config.js):

async headers() {
  return [{
    source: '/(.*)',
    headers: [
      { key: 'Strict-Transport-Security', value: 'max-age=31536000; includeSubDomains; preload' },
    ],
  }];
},

Content Security Policy (CSP)

The attack it prevents: Cross-site scripting (XSS), clickjacking, data injection, and malicious resource loading.

Content Security Policy is the most powerful and most complex security header. It defines an allowlist of sources from which the browser is permitted to load scripts, styles, images, fonts, and other resources. Inline scripts and eval() are blocked by default when a CSP is in place, which eliminates the most common XSS attack vectors.

A practical starting policy

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'; base-uri 'self'; form-action 'self'

Breaking this down:

  • default-src 'self' sets a catch-all that only permits resources from your own origin.
  • script-src 'self' blocks inline scripts and external scripts from untrusted origins.
  • style-src 'self' 'unsafe-inline' allows inline styles, which many CSS frameworks require.
  • frame-ancestors 'none' prevents your page from being embedded in any iframe.
  • base-uri 'self' prevents attackers from injecting a base tag to redirect relative URLs.
  • form-action 'self' restricts where forms can submit, blocking phishing redirects.

Report-Only mode for safe rollout

Before enforcing a CSP on a live site, use Content-Security-Policy-Report-Only with a report-uri or report-to directive. Violations are logged but not blocked, letting you catch legitimate resources that would be blocked before you enable enforcement.


X-Content-Type-Options

The attack it prevents: MIME sniffing attacks, where a browser ignores the declared Content-Type and guesses the file type, potentially executing a malicious file as a script.

This header has a single valid value:

X-Content-Type-Options: nosniff

With nosniff set, browsers will not attempt to guess MIME types. If a response says it is text/plain, the browser will treat it as text even if it looks like JavaScript.


X-Frame-Options

The attack it prevents: Clickjacking, where your site is loaded inside an invisible iframe overlaid on top of a malicious page to trick users into clicking buttons they cannot see.

X-Frame-Options: DENY
  • DENY prevents your page from being framed by any origin, including your own.
  • SAMEORIGIN allows framing only by pages on the same origin.

If you have a CSP with frame-ancestors, that directive takes precedence in modern browsers. Keep both for compatibility with older browsers.


Referrer-Policy

The attack it prevents: Accidental leakage of sensitive URL parameters (session tokens, search queries, internal paths) to third-party sites via the HTTP Referer header.

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

This is the recommended default. It sends the full URL for same-origin requests but only the origin for cross-origin requests, and nothing when navigating from HTTPS to HTTP.

Other useful values:

  • no-referrer sends nothing, maximizing privacy at the cost of losing referral analytics.
  • same-origin sends the full URL only to same-origin destinations.
  • strict-origin sends only the origin to all destinations.

Permissions-Policy

The attack it prevents: Unauthorized use of browser APIs — camera, microphone, geolocation, payment, and others — by your page or embedded third-party iframes.

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

Each directive takes a list of allowed origins in parentheses. An empty list () disables the feature entirely. Allow your own origin with self:

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

X-XSS-Protection

X-XSS-Protection: 0

This header is largely deprecated. Modern browsers have removed their built-in XSS auditors because they were inconsistent and could themselves be exploited. Set this to 0 to explicitly disable the feature, and rely on a strong CSP instead.


Common Configuration Mistakes

Setting headers only on HTML responses. Security headers need to be present on all responses including API routes, static assets, and redirects.

Using CSP with 'unsafe-eval' everywhere. Adding 'unsafe-eval' defeats much of the XSS protection CSP provides. Audit your dependencies for eval usage and replace them.

Forgetting the 'always' directive in nginx. Without always, nginx only sends the header on 200-status responses. Error pages and redirects will be missing headers.

Setting HSTS before HTTPS is fully functional. If any subdomain cannot serve HTTPS, adding includeSubDomains will break those resources for up to the max-age duration.

Using a CSP without testing in Report-Only mode first. Enforcing a CSP on a complex application without a testing phase routinely breaks analytics, chat widgets, payment forms, and other third-party integrations.


How SiteProbe Grades Security Headers

SiteProbe analyzes the full HTTP response from your domain and scores each header category independently using a weighted model:

  • HSTS is weighted heavily because it protects the connection itself
  • CSP is evaluated for presence, directive completeness, and the absence of unsafe fallbacks
  • X-Content-Type-Options, X-Frame-Options, and Referrer-Policy are treated as baseline requirements
  • Permissions-Policy is checked for presence and whether high-risk features are explicitly addressed
  • X-XSS-Protection being set to 0 is treated as correct

An A+ requires full marks across all graded headers.


Step-by-Step Guide to an A+ Grade

flowchart TD
    A[Step 1: Scan current headers] --> B[Step 2: Enable HTTPS + HSTS]
    B --> C[Step 3: Add simple headers]
    C --> D[Step 4: Configure Permissions-Policy]
    D --> E[Step 5: Build CSP in Report-Only]
    E --> F[Step 6: Enforce CSP]
    F --> G[Step 7: Set X-XSS-Protection to 0]
    G --> H[Step 8: Rescan with SiteProbe]
    H --> I[Step 9: Submit HSTS preload]
  1. Scan your current headers at siteprobe.live to get a baseline
  2. Enable HTTPS everywhere and deploy HSTS with max-age=31536000; includeSubDomains
  3. Add the simple headers — X-Content-Type-Options, X-Frame-Options, Referrer-Policy are single-line additions
  4. Configure Permissions-Policy — deny all browser features your application does not use
  5. Build your CSP in Report-Only mode — observe violations over a week of real traffic
  6. Enforce the CSP — switch from Report-Only to enforced
  7. Set X-XSS-Protection to 0 — one line, no tradeoffs
  8. Rescan with SiteProbe to confirm every header is correctly configured
  9. Add HSTS preload and submit your domain at hstspreload.org

Cloudflare Configuration

If you are proxying through Cloudflare, you can set security headers in a Transform Rule without touching your origin server. Go to Rules > Transform Rules > Modify Response Header, add each header as a Set operation, and apply the rule to all incoming requests matching your hostname.

Cloudflare also provides an automatic HSTS toggle under SSL/TLS > Edge Certificates, but configuring the full header yourself via Transform Rules gives you more control over the directives.


Security headers are a defensive layer that requires no ongoing maintenance once correctly configured. A thirty-minute investment in configuration protects every user who visits your site from the moment you deploy. Run a free scan at SiteProbe to see exactly where your site stands today.

Check your domain now

See how your domain scores on SSL, security headers, and more.
Then set up monitoring alerts to catch problems early.

$
Security Headers Explained: The Complete Guide | SiteProbe