August 20, 2025

Web Security Headers: What They Do and How to Configure Them

A practical, copy-paste guide for Nginx, Apache, Cloudflare, Node/Express, Vercel, and Netlify

Web Security Headers: What They Do and How to Configure Them

Small, surgical changes to your HTTP response headers can block whole classes of attacks and improve privacy for your users. This guide explains five essential headers — X-Frame-Options, X-Content-Type-Options, Strict-Transport-Security, Referrer-Policy, and Permissions-Policy — plus exact, copy-paste snippets for popular stacks.

Why Security Headers Matter

These headers work at the browser level to prevent clickjacking, MIME sniffing, protocol downgrades, data over-sharing, and unauthorized hardware/API access. They are low-risk to deploy (with a few caveats for HSTS) and provide immediate, measurable defenses.

Recommended Baseline (Safe Defaults)

  • X-Frame-Options: DENY (or SAMEORIGIN if your site must embed itself).
  • X-Content-Type-Options: nosniff.
  • Strict-Transport-Security: max-age=31536000; includeSubDomains; preload (HTTPS-only sites; start smaller during rollout).
  • Referrer-Policy: strict-origin-when-cross-origin (use no-referrer for maximum privacy if analytics allow).
  • Permissions-Policy: disable unused features, e.g. accelerometer=(), autoplay=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=().

Tip: If you already use a strong Content-Security-Policy with frame-ancestors, prefer that over X-Frame-Options (keep XFO for legacy browsers if desired).

X-Frame-Options (Anti-Clickjacking)

What it does: Prevents your pages from being embedded in iframes on malicious origins that might trick users into clicking hidden UI (clickjacking).

Recommended values: DENY blocks all framing. SAMEORIGIN allows iframes from your own origin (useful for admin dashboards or widgets).

Risk without it: Attackers can overlay your site in an invisible frame and capture clicks or keystrokes.

How to add:

Nginx

add_header X-Frame-Options "SAMEORIGIN" always;

Apache (.htaccess or vhost)

<IfModule mod_headers.c>
  Header always set X-Frame-Options "SAMEORIGIN"
</IfModule>

Cloudflare (Response Header Modification)

  • Rules → Transform Rules → Response Header Modification → Add
  • Header: X-Frame-Options, Value: SAMEORIGIN

Node/Express (helmet)

// npm i helmet
import helmet from "helmet";
app.use(helmet({ frameguard: { action: "sameorigin" } }));

Vercel (vercel.json)

{
  "headers": [
    { "source": "/(.*)", "headers": [
      { "key": "X-Frame-Options", "value": "SAMEORIGIN" }
    ] }
  ]
}

Netlify (netlify.toml)

[[headers]]
for = "/*"
  [headers.values]
  X-Frame-Options = "SAMEORIGIN"

Notes: If you must allow specific external embedders, use CSP’s frame-ancestors instead of loosening XFO across the board.

X-Content-Type-Options (No MIME Sniffing)

What it does: Stops browsers from “sniffing” content types and interpreting files as something else (e.g., serving user-uploaded text as executable script).

Recommended value: nosniff.

Risk without it: Uploaded or proxied files may execute unexpectedly in some browsers.

How to add:

Nginx

add_header X-Content-Type-Options "nosniff" always;

Apache

<IfModule mod_headers.c>
  Header always set X-Content-Type-Options "nosniff"
</IfModule>

Cloudflare

  • Add header: X-Content-Type-Optionsnosniff

Node/Express (helmet)

import helmet from "helmet";
app.use(helmet({ contentTypeOptions: true })); // sends X-Content-Type-Options: nosniff

Vercel / Netlify

// Vercel header
{ "key": "X-Content-Type-Options", "value": "nosniff" }

// Netlify
X-Content-Type-Options = "nosniff"

Strict-Transport-Security (HSTS, HTTPS-Only)

What it does: Forces browsers to use HTTPS for your domain (and optionally subdomains), preventing protocol downgrades and certain MITM attacks.

Recommended value: max-age=31536000; includeSubDomains; preload for mature, HTTPS-only estates. During initial rollout, start safer with max-age=86400 and without preload.

Risk without it: Attackers can attempt SSL stripping or downgrade users to HTTP.

Pre-deployment checklist: Confirm all subdomains serve valid HTTPS; confirm redirects and CDNs won’t break. Understand that preload submits your domain to browser lists and is hard to roll back.

How to add:

Nginx

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

Apache

<IfModule mod_headers.c>
  Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
</IfModule>

Cloudflare

  • SSL/TLS → Edge Certificates → HTTP Strict Transport Security (HSTS) → Enable and configure.

Node/Express (helmet)

import helmet from "helmet";
app.use(helmet.hsts({ maxAge: 31536000, includeSubDomains: true, preload: true }));

Vercel / Netlify

// Vercel header
{ "key": "Strict-Transport-Security", "value": "max-age=31536000; includeSubDomains; preload" }

// Netlify
Strict-Transport-Security = "max-age=31536000; includeSubDomains; preload"

Notes: Only set HSTS on HTTPS responses (ideally at the CDN/edge). Never add HSTS to HTTP because clients won’t see it in time.

Referrer-Policy (Privacy & Analytics Balance)

What it does: Controls how much referrer information the browser sends to other sites when users follow links.

Recommended value: strict-origin-when-cross-origin — preserves full path on same-origin, but only the origin for cross-origin requests (good privacy/analytics balance). For maximum privacy use no-referrer.

Risk without it: Unintended leakage of sensitive path/query data to third-party sites.

How to add:

Nginx

add_header Referrer-Policy "strict-origin-when-cross-origin" always;

Apache

<IfModule mod_headers.c>
  Header always set Referrer-Policy "strict-origin-when-cross-origin"
</IfModule>

Cloudflare

  • Add header: Referrer-Policystrict-origin-when-cross-origin

Node/Express (helmet)

import helmet from "helmet";
app.use(helmet({ referrerPolicy: { policy: "strict-origin-when-cross-origin" } }));

Vercel / Netlify

// Vercel header
{ "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" }

// Netlify
Referrer-Policy = "strict-origin-when-cross-origin"

Permissions-Policy (Limit Powerful Browser Features)

What it does: Controls access to sensitive APIs and hardware (camera, mic, geolocation, autoplay, etc.). It prevents unused features from being abused by third-party widgets or compromised scripts.

Recommended baseline: Deny everything you do not explicitly need:

accelerometer=(), autoplay=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()

Risk without it: Third-party content may prompt or access capabilities you didn’t intend to expose.

How to add:

Nginx

add_header Permissions-Policy "accelerometer=(), autoplay=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()" always;

Apache

<IfModule mod_headers.c>
  Header always set Permissions-Policy "accelerometer=(), autoplay=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"
</IfModule>

Cloudflare

  • Add header: Permissions-Policy → value as above.

Node/Express

// helmet v7 may not include a helper for Permissions-Policy; set it manually:
app.use((_, res, next) => {
  res.setHeader("Permissions-Policy", "accelerometer=(), autoplay=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()");
  next();
});

Vercel / Netlify

// Vercel header
{ "key": "Permissions-Policy", "value": "accelerometer=(), autoplay=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()" }

// Netlify
Permissions-Policy = "accelerometer=(), autoplay=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"

Notes: If you need to allow a capability, scope it narrowly (e.g., camera=(self "https://video.example")).

All-in-One Snippets (Copy/Paste)

Nginx (inside HTTPS server block)

add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "accelerometer=(), autoplay=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()" always;

Apache (vhost or .htaccess; requires mod_headers)

<IfModule mod_headers.c>
  Header always set X-Frame-Options "SAMEORIGIN"
  Header always set X-Content-Type-Options "nosniff"
  Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
  Header always set Referrer-Policy "strict-origin-when-cross-origin"
  Header always set Permissions-Policy "accelerometer=(), autoplay=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"
</IfModule>

Cloudflare (Edge)

  • Rules → Response Header Modification → Add each header/value above.
  • HSTS: SSL/TLS → Edge Certificates → HTTP Strict Transport Security.

Node/Express

// npm i helmet
import helmet from "helmet";
app.use(helmet({
  frameguard: { action: "sameorigin" },       // or 'deny'
  contentTypeOptions: true,                    // nosniff
  hsts: { maxAge: 31536000, includeSubDomains: true, preload: true },
  referrerPolicy: { policy: "strict-origin-when-cross-origin" }
}));
// Permissions-Policy manual header:
app.use((_, res, next) => {
  res.setHeader("Permissions-Policy", "accelerometer=(), autoplay=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()");
  next();
});

Vercel (vercel.json)

{
  "headers": [
    { "source": "/(.*)", "headers": [
      { "key": "X-Frame-Options", "value": "SAMEORIGIN" },
      { "key": "X-Content-Type-Options", "value": "nosniff" },
      { "key": "Strict-Transport-Security", "value": "max-age=31536000; includeSubDomains; preload" },
      { "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" },
      { "key": "Permissions-Policy", "value": "accelerometer=(), autoplay=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()" }
    ] }
  ]
}

Netlify (netlify.toml)

[[headers]]
for = "/*"
  [headers.values]
  X-Frame-Options = "SAMEORIGIN"
  X-Content-Type-Options = "nosniff"
  Strict-Transport-Security = "max-age=31536000; includeSubDomains; preload"
  Referrer-Policy = "strict-origin-when-cross-origin"
  Permissions-Policy = "accelerometer=(), autoplay=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"

Deployment Order, Rollout, and Caveats

  1. Start with X-Content-Type-Options and Referrer-Policy (low risk).
  2. Add X-Frame-Options (verify iframes still work; consider CSP frame-ancestors for granular allow-lists).
  3. Roll out Permissions-Policy (deny all, then open specific features as needed).
  4. Deploy HSTS last: begin with max-age=86400, verify logs and redirects, then increase to a year and consider preload.

Common pitfalls: mixed content, header duplication at CDN and origin, caching stale headers, enabling HSTS when some subdomains still serve HTTP, or breaking embedded integrations with too-strict framing rules.

Testing and Verification

  • Command line: curl -I https://yourdomain.com and check headers.
  • Automated scanners: use multiple tools (observatory, securityheaders analyzers) to catch misconfigurations.
  • Real-user testing: validate journeys involving iframes, payments, SSO, and third-party widgets.
  • Monitoring: watch error rates, CSP reports (if using CSP), and browser console warnings.

Quick Mapping: Threat → Header

  • Clickjacking → X-Frame-Options (or CSP frame-ancestors)
  • MIME sniffing → X-Content-Type-Options
  • Protocol downgrade / SSL stripping → HSTS
  • Leaky referral paths → Referrer-Policy
  • Unwanted hardware/API access → Permissions-Policy

Summary

Implementing these five headers with the recommended values provides strong, immediate protection with minimal work. Use the all-in-one snippets above, ship in stages, and monitor. For most sites, this baseline is a “must-have” — simple, robust, and future-proof.

If you want to quickly check your website or any other website for security settings, install the SalesPilot extension, which will show you all the areas that require your attention.

Chat with me. I'm online!
Eugene Orel
Ai Specialist
Hello, my name is Eugene, I can answer your questions about the extension SalesPilot. What are you interested in?
10:06