ALL POSTS

Security Headers Explained: Practical Setup Guide

October 19, 2026 · 6 min read

website-securityheaders
Layered translucent shields protecting an abstract browser window.

Security headers are instructions your website sends to a browser alongside a response. They can limit framing, reduce information leakage, and constrain which resources a page may execute or load. They do not repair a broken permission check or an exposed database. The useful goal is a policy that fits your application and survives your deployment process.

This guide explains the HTTP security headers most small teams should review, then shows where to configure a practical baseline. Apply the examples to your own site, test on staging, and inspect the public response after deployment. Your reverse proxy or CDN may change what the application sends.

Start with the actual response

Use a GET request and discard the body so you see the headers a normal page request receives:

curl -sS -o /dev/null -D - https://example.com/
curl -sS -o /dev/null -D - https://example.com/login

Repeat for a missing route and an application error you can safely reproduce. A header present on a cached landing page may be absent on dynamically rendered pages. Record which layer owns each header; duplicate policies are often harder to debug than missing ones.

HSTS: make HTTPS the remembered default

The HSTS header tells browsers to use HTTPS for a host after receiving the policy over a valid HTTPS connection. Start with a short max-age while checking certificate renewal and redirects. Extend the duration only after those processes work reliably.

Strict-Transport-Security: max-age=300

Do not add includeSubDomains until every covered hostname can use HTTPS. A forgotten customer portal or legacy mail interface can make that change disruptive. Preloading is a separate decision with additional requirements and a slower reversal path. Consult the HSTS standard when choosing scope.

Content-Security-Policy: define the browser’s resource boundaries

CSP can restrict scripts, styles, connections, images, frames, and other resource types. It needs an application-specific design. Begin with Content-Security-Policy-Report-Only, observe violations, and exercise login, payments, analytics, uploads, and embedded content.

Content-Security-Policy-Report-Only: default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'

This is a diagnostic starting point, not a universal production policy. It will report legitimate inline scripts and third-party resources in many applications. A nonce-based Next.js policy also changes rendering and caching requirements. Keep that work separate from installing a few static headers.

X-Frame-Options and frame-ancestors

If your site should never be embedded, X-Frame-Options: DENY is a straightforward legacy framing control. If same-origin embedding is required, consider SAMEORIGIN. Modern CSP provides frame-ancestors, which can express a more specific allowlist.

Decide whether customer dashboards, payment pages, or partner portals genuinely need embedding. Use an enforced frame-ancestors policy for that contract, and avoid contradictory X-Frame-Options settings. Report-only CSP does not enforce a framing restriction. The OWASP header guidance provides context for these controls.

X-Content-Type-Options

Set X-Content-Type-Options: nosniff and serve correct content types. This limits browser MIME sniffing behavior in relevant contexts. It does not make arbitrary user uploads safe. Keep uploads isolated, validate expected types server-side, and return a suitable Content-Disposition when files should download rather than render.

Referrer-Policy

strict-origin-when-cross-origin is a reasonable starting point for many sites: it limits cross-origin referrer detail while retaining useful same-origin behavior. no-referrer is stricter and may suit sensitive areas. Neither is a reason to put tokens or confidential values in URLs. Inspect your analytics and integrations after changing the policy.

Permissions-Policy

Disable browser capabilities your site does not use, rather than copying a huge list of directives from an old article:

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

A video-calling application needs a different policy. Treat each capability as a product requirement and test it in supported browsers. Unsupported directives do not automatically mean the entire header provides useful protection.

Nginx: a small baseline

Place these directives in the relevant HTTPS server configuration. Retain existing TLS, routing, and proxy settings.

add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header X-Frame-Options "DENY" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header Strict-Transport-Security "max-age=300" always;

Test configuration syntax before reloading. Check nested locations carefully: add_header inheritance can change when a child location defines its own headers, and behavior depends on your configuration and Nginx version. The Nginx headers module documentation explains always and inheritance. Verify public responses rather than assuming the file placement is enough.

Next.js: static headers in next.config

For headers that do not vary per request, use the supported headers() configuration:

import type { NextConfig } from 'next';

const config: NextConfig = {
  async headers() {
    return [{
      source: '/:path*',
      headers: [
        { key: 'X-Content-Type-Options', value: 'nosniff' },
        { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
        { key: 'X-Frame-Options', value: 'DENY' },
      ],
    }];
  },
};
export default config;

Merge this into the existing configuration instead of replacing plugins or other options. Rebuild and inspect the deployed response. See the Next.js headers API for matching rules. Per-request CSP nonces belong in a different implementation path.

Express: use Helmet, then review its policy

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

const app = express();
app.use(helmet());
// Register your application routes after the header middleware.

Helmet sets several headers and includes a CSP by default. Inspect its current defaults before deploying; an application with inline scripts or embedded content may need explicit configuration. Mounting middleware after a route that already sends a response will not protect that response. Handle proxy and application headers consistently.

Vercel and Cloudflare

On Vercel, a Next.js application can use its framework configuration; other deployments can use the platform’s header configuration. Cloudflare provides response header transformation rules. In either case, choose one policy owner and check for conflicting origin headers. Edge configuration should be tracked and reviewed like application code.

For a non-Next.js Vercel deployment, a minimal vercel.json example is:

{
  "headers": [{
    "source": "/(.*)",
    "headers": [
      { "key": "X-Content-Type-Options", "value": "nosniff" },
      { "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" }
    ]
  }]
}

On Cloudflare, create a response-header modification rule scoped to your hostname, such as the expression below, and set those two header names to the same static values. Confirm rule availability for your plan and retain any existing rules that your application requires.

http.host eq "example.com"

Check security headers after deployment

Compare before and after responses, then use a browser to complete real tasks. Keep a rollback ready for CSP, framing, or permissions changes. Review exceptions explicitly instead of weakening every page to accommodate one integration. Headers are one part of the broader website security checklist, and a high grade does not establish overall application security.

Check your own site — your first scan is free. Scanwise combines passive intelligence with non-destructive external checks and provides AI-assisted fix suggestions. Its HTTP, TLS, and TCP probes do not exploit findings or modify your site. Verify suggested changes on staging; starter credits require a verified account and remain subject to anti-abuse limits.

Check your own site

Run the same external recon on your own domain. First scan is free.

RUN AI SCAN — FIRST SCAN FREE