Skip to main content
Back to blog
March 10, 2026|9 min read|Hardening Guides

How to Fix Missing Security Headers: A Practical Guide for Every Platform

Step-by-step guide to adding the 6 most important HTTP security headers. Works for Nginx, Apache, Cloudflare, Vercel, and Next.js.

ZeriFlow Team

1,375 words

Why Security Headers Matter

HTTP security headers are instructions your server sends to browsers, telling them how to behave when handling your site's content. They are your first line of defense against common web attacks: cross-site scripting (XSS), clickjacking, MIME-type sniffing, and protocol downgrade attacks.

The problem? Most websites are missing some or all of them. A 2025 study by the HTTP Archive found that only 12% of websites serve a Content-Security-Policy header, and only 25% serve Strict-Transport-Security.

These headers cost nothing to implement, require no code changes, and can be deployed in minutes. There is no reason not to have them.

The 6 Headers You Must Have

Here are the six security headers every website needs, in order of importance:

1. Strict-Transport-Security (HSTS)

Forces HTTPS connections. Prevents protocol downgrade attacks.

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
  • max-age=31536000 — enforce for one year
  • includeSubDomains — applies to all subdomains
  • preload — submit to browser preload lists

2. Content-Security-Policy (CSP)

Controls which resources the browser is allowed to load. The single most effective header against XSS attacks.

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';

A strict CSP looks like this:

Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self'; font-src 'self'; connect-src 'self'; base-uri 'self'; form-action 'self'; frame-ancestors 'none';

Start with report-only mode to avoid breaking your site:

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

3. X-Content-Type-Options

Prevents MIME-type sniffing. Browsers will only use the Content-Type you declare.

X-Content-Type-Options: nosniff

One value, one purpose. This is the easiest header to add.

4. X-Frame-Options

Prevents your site from being embedded in iframes on other domains (clickjacking protection).

X-Frame-Options: DENY

Use DENY to block all framing, or SAMEORIGIN to allow framing only by your own domain.

Note: frame-ancestors in CSP supersedes X-Frame-Options, but include both for backward compatibility with older browsers.

5. Referrer-Policy

Controls how much referrer information is shared when users navigate away from your site.

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

This sends the full URL for same-origin requests and only the origin (domain) for cross-origin requests. It is a good balance between privacy and functionality.

6. Permissions-Policy

Controls which browser features your site can use (camera, microphone, geolocation, etc.).

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

Empty parentheses () disable the feature entirely. If your site does not use the camera, declare it — this prevents malicious scripts from accessing it if they somehow execute on your page.

How to Add Headers on Nginx

Add all six headers to your server block:

nginx
server {
    listen 443 ssl http2;
    server_name yoursite.com;

    # Security Headers
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" 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'; connect-src 'self'; frame-ancestors 'none';" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "DENY" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()" always;

    # SSL config
    ssl_certificate /etc/ssl/certs/yoursite.crt;
    ssl_certificate_key /etc/ssl/private/yoursite.key;

    location / {
        # ... your config
    }
}

Important Nginx caveat: If you use add_header inside a location block, it overrides all headers set at the server level. Either set all headers at the server level, or repeat them in every location block. Alternatively, use the ngx_headers_more module:

nginx
more_set_headers "X-Content-Type-Options: nosniff";

Test and reload:

bash
nginx -t && systemctl reload nginx

How to Add Headers on Apache

Enable mod_headers and add the headers to your VirtualHost or .htaccess:

apache
<IfModule mod_headers.c>
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
    Header always set 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';"
    Header always set X-Content-Type-Options "nosniff"
    Header always set X-Frame-Options "DENY"
    Header always set Referrer-Policy "strict-origin-when-cross-origin"
    Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()"
</IfModule>

Enable the module if it is not already active:

bash
a2enmod headers
systemctl restart apache2

How to Add Headers on Cloudflare

Cloudflare offers multiple approaches:

Transform Rules (easiest)

  1. 1Go to RulesTransform RulesModify Response Header
  2. 2Create a new rule matching all traffic (*)
  3. 3Add each header as a "Set static" action

Cloudflare Workers (most flexible)

javascript
export default {
  async fetch(request) {
    const response = await fetch(request);
    const newResponse = new Response(response.body, response);

    newResponse.headers.set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
    newResponse.headers.set('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';");
    newResponse.headers.set('X-Content-Type-Options', 'nosniff');
    newResponse.headers.set('X-Frame-Options', 'DENY');
    newResponse.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
    newResponse.headers.set('Permissions-Policy', 'camera=(), microphone=(), geolocation=(), payment=()');

    return newResponse;
  },
};

HSTS specifically

Cloudflare has a dedicated HSTS toggle under SSL/TLSEdge Certificates. Use it for HSTS and Transform Rules for the rest.

How to Add Headers on Vercel / Next.js

In next.config.js:

javascript
/** @type {import('next').NextConfig} */
const nextConfig = {
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          {
            key: 'Strict-Transport-Security',
            value: 'max-age=31536000; includeSubDomains; preload',
          },
          {
            key: 'Content-Security-Policy',
            value: "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';",
          },
          {
            key: 'X-Content-Type-Options',
            value: 'nosniff',
          },
          {
            key: 'X-Frame-Options',
            value: 'DENY',
          },
          {
            key: 'Referrer-Policy',
            value: 'strict-origin-when-cross-origin',
          },
          {
            key: 'Permissions-Policy',
            value: 'camera=(), microphone=(), geolocation=(), payment=()',
          },
        ],
      },
    ];
  },
};

module.exports = nextConfig;

Alternatively, use middleware for dynamic CSP with nonces:

javascript
// middleware.ts
import { NextResponse } from 'next/server';

export function middleware(request) {
  const nonce = Buffer.from(crypto.randomUUID()).toString('base64');
  const csp = `default-src 'self'; script-src 'self' 'nonce-${nonce}'; style-src 'self' 'unsafe-inline';`;

  const response = NextResponse.next();
  response.headers.set('Content-Security-Policy', csp);
  response.headers.set('X-Content-Type-Options', 'nosniff');
  response.headers.set('X-Frame-Options', 'DENY');
  response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
  response.headers.set('Permissions-Policy', 'camera=(), microphone=(), geolocation=(), payment=()');

  return response;
}

How to Verify Your Headers

Command line

bash
curl -sI https://yoursite.com | grep -iE '(strict-transport|content-security|x-content-type|x-frame|referrer-policy|permissions-policy)'

Quick verification table

HeaderExpected valueCheck
Strict-Transport-Securitymax-age=31536000; ...Present and max-age >= 31536000
Content-Security-Policydefault-src 'self'; ...Present and not empty
X-Content-Type-OptionsnosniffExact match
X-Frame-OptionsDENY or SAMEORIGINPresent
Referrer-Policystrict-origin-when-cross-originPresent
Permissions-Policycamera=(), ...Present

Automated scanning

The fastest way to check all headers at once is to run a scan at zeriflow.com. The scanner checks all six headers plus additional security configurations, and gives you specific remediation steps for anything that is missing.

Next Steps

After adding these headers:

  1. 1Test your site thoroughly — CSP in particular can break functionality if too restrictive
  2. 2Monitor CSP violations — Use report-uri or report-to directives to catch issues
  3. 3Increase HSTS max-age gradually — Start small, then scale up
  4. 4Run a full security scan — Headers are just one part of your security posture. Run a comprehensive scan at zeriflow.com to check SSL/TLS, cookies, DNS, and more

Security headers are the lowest-effort, highest-impact security improvement you can make to any website. There is no reason to leave them missing.

Ready to check your site?

Run a free security scan in 30 seconds.

Related articles

Keep reading