Skip to main content
Back to blog
March 14, 2026·Updated May 2, 2026|10 min read|Antoine Duno|Web Security

HTTP Security Headers Explained: The Complete Developer Guide (2026)

HTTP security headers are free, fast to deploy, and fix a wide range of browser-level vulnerabilities. This guide covers all seven essential headers, what each one does, how to add them to your server, and how to verify they are working.

Antoine Duno

1,964 words

AD

Antoine Duno

Founder of ZeriFlow · 10 years fullstack engineering · About the author

Key Takeaways

  • HTTP security headers are free, fast to deploy, and fix a wide range of browser-level vulnerabilities. This guide covers all seven essential headers, what each one does, how to add them to your server, and how to verify they are working.
  • Includes copy-paste code examples and step-by-step instructions.
  • Free automated scan available to verify your implementation.

HTTP Security Headers Explained: The Complete Developer Guide (2026)

HTTP security headers are one of the highest-leverage security improvements you can make to any web application. They are free, they do not require application code changes in most cases, and they can be deployed in under an hour. Yet a significant majority of production websites are missing at least three of the seven core headers.

This guide walks through every header you need, explains exactly what it does, and shows you how to add it — with copy-paste examples for Nginx, Apache, and Node.js/Express.


Why Security Headers Matter

Browsers have built-in security mechanisms that are disabled by default. Security headers are the server-side instructions that activate those mechanisms for your site. Without them:

  • Attackers can load your site in an iframe and trick users into clicking hidden buttons (clickjacking)
  • Malicious scripts injected via XSS can load resources from any domain
  • Browsers may sniff MIME types and execute a text file as JavaScript
  • Your users'' traffic can be downgraded from HTTPS to HTTP mid-connection

None of these are application bugs. They are gaps in browser security that headers close.


How to Check Your Current Security Headers

Before you start adding headers, check what you already have. You can do this in several ways:

Browser DevTools: Open Network tab, click any request to your domain, look at the Response Headers section.

Command line:

bash
curl -I https://yourdomain.com

Automated scan: Run a free check at ZeriFlow — it tests all headers simultaneously and shows which are missing, misconfigured, or outdated.


The 7 Essential HTTP Security Headers

1. Content-Security-Policy (CSP)

What it does: Tells the browser exactly which sources are allowed to load scripts, styles, images, fonts, and other resources. Any resource that does not match the policy is blocked. This is the primary defence against Cross-Site Scripting (XSS) attacks.

Basic example:

http
Content-Security-Policy: default-src ''self''; script-src ''self'' https://cdn.yourdomain.com; style-src ''self'' ''unsafe-inline''; img-src ''self'' data:; frame-ancestors ''none''

Safe starting point (report-only, won''t break anything):

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

Key directives: - default-src — fallback for all resource types - script-src — controls JavaScript sources - style-src — controls CSS sources - img-src — controls image sources - frame-ancestors — controls which pages can embed yours in an iframe (supersedes X-Frame-Options in modern browsers) - report-uri / report-to — where to send violation reports

CSP is covered in depth in the dedicated CSP guide on this blog.


2. Strict-Transport-Security (HSTS)

What it does: Tells browsers to only access your site over HTTPS, even if the user types http://. Once a browser has seen an HSTS header, it will automatically upgrade all future requests to HTTPS for the duration of max-age — without ever making an HTTP request first.

This eliminates SSL stripping attacks, where a man-in-the-middle intercepts the initial HTTP request before it can redirect to HTTPS.

Recommended configuration:

http
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
  • max-age=31536000 — 1 year in seconds; this is the minimum for HSTS preload submission
  • includeSubDomains — applies HSTS to all subdomains
  • preload — signals intent to be included in browser preload lists (requires separate submission at hstspreload.org)

Do not set this on a site that is not fully on HTTPS. It will lock your users out of HTTP on that domain for the max-age duration.


3. X-Frame-Options

What it does: Prevents your page from being embedded in an <iframe> or <frame> on another domain. This blocks clickjacking attacks, where an attacker overlays a transparent iframe over a decoy page to capture clicks.

Values:

http
X-Frame-Options: DENY          # Never allow framing
X-Frame-Options: SAMEORIGIN    # Only allow framing by same origin

ALLOWFROM was a third option (allow a specific origin) but it was never consistently implemented and is now deprecated. If you need to allow specific origins to frame your content, use CSP''s frame-ancestors directive instead.

Note: CSP frame-ancestors supersedes X-Frame-Options in browsers that support it, but you should set both for backwards compatibility.


4. X-Content-Type-Options

What it does: Prevents browsers from guessing (sniffing) the MIME type of a response when it differs from the declared Content-Type. Without this header, a browser might execute a text file as JavaScript if it detects JavaScript-like content — a technique used in MIME confusion attacks.

The value is always:

http
X-Content-Type-Options: nosniff

This header has zero downside and takes five seconds to add. There is no reason any site should be missing it.


5. Referrer-Policy

What it does: Controls how much of the URL in the Referer (yes, it''s historically misspelt) header is sent to third parties when a user navigates from your page to an external site. Without a policy, the full URL — including query parameters, which may contain user IDs, session tokens, or search terms — is sent with every outbound request.

Common values and their meanings:

ValueWhat gets sent externally
no-referrerNothing
no-referrer-when-downgradeFull URL to HTTPS, nothing to HTTP (browser default)
same-originFull URL only to same origin; nothing cross-origin
strict-originJust the origin (no path) to HTTPS; nothing to HTTP
strict-origin-when-cross-originFull URL same-origin; just origin cross-origin

Recommended:

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

This is a good balance — it preserves referrer information for same-origin analytics while preventing path and query parameter leakage to third parties.


6. Permissions-Policy

What it does: Allows you to explicitly disable browser features that your site does not use — things like the camera, microphone, geolocation, payment APIs, USB access, and more. Even if an XSS attack injects malicious JavaScript, it cannot access features you have blocked at the policy level.

Example (restrictive policy for a typical SaaS):

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

An empty () means the feature is disabled entirely. (self) means only the current origin can use it.

This header replaced the older Feature-Policy header. If you are still using Feature-Policy, replace it.


7. Cross-Origin-Opener-Policy (COOP)

What it does: Isolates your page''s browsing context from third-party popup windows. This is important for defence against Spectre-class side-channel attacks and cross-origin information leakage.

Values:

http
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Opener-Policy: same-origin-allow-popups
Cross-Origin-Opener-Policy: unsafe-none

same-origin provides the strongest isolation. same-origin-allow-popups is needed if your site uses OAuth flows or third-party popups.

Pairing COOP with Cross-Origin-Embedder-Policy: require-corp (COEP) enables SharedArrayBuffer and high-resolution timers, which are needed for certain web performance and WebAssembly use cases.


How to Add Security Headers

Nginx

nginx
server {
    # ... your existing config ...

    # 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 Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()" always;
    add_header Cross-Origin-Opener-Policy "same-origin-allow-popups" always;
    add_header Content-Security-Policy "default-src ''self''; script-src ''self''; style-src ''self'' ''unsafe-inline''; img-src ''self'' data: https:; font-src ''self''; frame-ancestors ''self''" always;
}

The always keyword ensures headers are added to all responses, including 4xx and 5xx error responses.

Apache

apache
<IfModule mod_headers.c>
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
    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"
    Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
    Header always set Cross-Origin-Opener-Policy "same-origin-allow-popups"
    Header always set Content-Security-Policy "default-src ''self''; script-src ''self''; style-src ''self'' ''unsafe-inline''; img-src ''self'' data: https:"
</IfModule>

Node.js / Express

The easiest approach is the helmet middleware, which sets sensible defaults for all security headers:

javascript
import helmet from ''helmet'';
import express from ''express'';

const app = express();

app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["''self''"],
      scriptSrc: ["''self''"],
      styleSrc: ["''self''", "''unsafe-inline''"],
      imgSrc: ["''self''", "data:", "https:"],
      fontSrc: ["''self''"],
      frameAncestors: ["''self''"],
    },
  },
  hsts: {
    maxAge: 31536000,
    includeSubDomains: true,
    preload: true,
  },
  referrerPolicy: { policy: ''strict-origin-when-cross-origin'' },
  permittedCrossDomainPolicies: false,
}));

Next.js

Add headers in next.config.js:

javascript
/** @type {import(''next'').NextConfig} */
const nextConfig = {
  async headers() {
    return [
      {
        source: ''/(.*)'',
        headers: [
          { 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: ''camera=(), microphone=(), geolocation=()'' },
          { key: ''Strict-Transport-Security'', value: ''max-age=31536000; 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:;"
          },
        ],
      },
    ];
  },
};

module.exports = nextConfig;

Note: Next.js requires ''unsafe-eval'' and ''unsafe-inline'' in CSP for development mode. Use environment-specific CSP values in production.


How to Test Your Headers

After deploying, verify your changes:

Quick terminal check:

bash
curl -s -I https://yourdomain.com | grep -i "security\\|content-security\\|strict-transport\\|x-frame\\|x-content\\|referrer\\|permissions\\|cross-origin"

Full automated scan: ZeriFlow checks all headers simultaneously, validates their values (not just their presence), and gives you a score with specific remediation guidance for anything that is missing or misconfigured.


Common Mistakes to Avoid

Setting CSP too loosely: Content-Security-Policy: default-src * is essentially the same as having no CSP at all. Make sure your policy actually restricts something.

Forgetting the `always` flag in Nginx: Without always, headers are only added to 200 responses. Error pages and redirects will be missing them.

Setting HSTS before your site is fully on HTTPS: If any page or subdomain is still HTTP, HSTS will break access to it for visitors who have already received the header.

Using X-Frame-Options ALLOWFROM: This was never properly implemented across browsers. Use CSP frame-ancestors for per-origin framing control.

Deploying CSP in enforcement mode without testing: CSP violations will silently break resources without any user-visible error in some cases, or cause hard failures in others. Always start with Content-Security-Policy-Report-Only.


Quick-Reference: Security Headers Checklist

HeaderMinimum Recommended ValueBreaks Anything?
Strict-Transport-Securitymax-age=31536000; includeSubDomainsOnly if site is not fully HTTPS
X-Frame-OptionsSAMEORIGINNo
X-Content-Type-OptionsnosniffNo
Referrer-Policystrict-origin-when-cross-originRarely (some third-party analytics)
Permissions-Policycamera=(), microphone=(), geolocation=()No
Cross-Origin-Opener-Policysame-origin-allow-popupsOnly if you use third-party popups
Content-Security-PolicyStart with report-onlyYes if not carefully configured

Security headers are the lowest-effort, highest-impact security improvement most sites can make. Start with the ones that carry no risk — X-Content-Type-Options, X-Frame-Options, Referrer-Policy, and Permissions-Policy — and work toward a complete CSP once those are in place.

If you want to see exactly which headers your site is missing right now, run a free scan at ZeriFlow.

Check all your security headers instantly — free.

See exactly which headers are missing and how to fix them.

Related articles

Keep reading