Back to Blog
Security
June 4, 2026· 5 min read

How to Remediate Missing HTTP Security Headers

Web security is an ever-shifting landscape where attackers constantly seek to exploit communication channels, client browsers, and asset deliveries. While many organizations focus heavily on backend defense (such as database firewalls and authentication gates), a shocking number of production environments remain vulnerable to front-end compromises. This exhaustive developer handbook details how to configure, deploy, test, and maintain a highly-hardened suite of HTTP response headers to defend against Cross-Site Scripting (XSS), Clickjacking, MIME sniffing, and session hijackings.


1The Foundational Threat Landscape: Why Response Headers Matter

When a user loads a website, the interaction is not a static rendering. The web browser is a complex runtime engine that interprets HTML documents, runs asynchronous JavaScript code, compiles stylesheets, parses fonts, fetches remote APIs, and processes third-party integrations. This multi-layered execution environment creates a broad surface for client-side attacks.

HTTP security headers are metadata parameters returned by the web server in its response headers. They act as explicit command sets informing the client browser how to restrict execution pathways, sandboxing potential payloads even if a malicious payload successfully breaches the application code itself. Without these headers, the browser operates under default permissive layouts, trusting content from arbitrary origins, executing mime-sniffed payloads, and permitting framing injections.

2Deep-Dive: The Six Critical Hardening Headers

A. Content Security Policy (CSP)

Content Security Policy (CSP) is arguably the most powerful client-side defense tool. CSP instructs the browser on which domains are trusted sources of assets (scripts, stylesheets, images, fonts, frames, media, and connections).

By default, a web browser will execute any JavaScript code it finds in the HTML document—including inline scripts (`<script>alert(1)</script>`) or scripts loaded from external domains. If an attacker discovers an input field that does not sanitize inputs, they can inject malicious code that runs in the browser session of every user who visits that page. This is Cross-Site Scripting (XSS).

A robust CSP breaks XSS by disabling inline script execution entirely and specifying an explicit whitelist of permitted source domains.

Content-Security-Policy: default-src 'self'; script-src 'self' https://apis.google.com; style-src 'self' 'unsafe-inline';

Let us examine key CSP directives:

  • default-src: Acts as a fallback for other fetch directives. If a directive (like `script-src` or `style-src`) is not explicitly defined, the browser falls back to the values specified in `default-src`.
  • script-src: Restricts valid locations where JavaScript files can be loaded and executed. Specifying `'self'` permits scripts only from the hosting origin. Specifying third-party domains (like `https://analytics.google.com`) authorizes them.
  • style-src: Directs where Cascading Style Sheets (CSS) can be loaded from. Note that using `'unsafe-inline'` is often required by legacy UI packages, but it is highly recommended to replace inline styles with static files or use CSS Nonces.
  • img-src: Limits the sources where image assets can be fetched. Highly useful to prevent data filtration attacks where an attacker attempts to send sensitive cookies via image requests to a tracking server.
  • connect-src: Restricts script interfaces that fetch data (like Fetch, XMLHttpRequest, WebSockets, or EventSource connections).
  • frame-ancestors: Restricts whether other websites can embed your pages inside `<iframe>`, `<frame>`, `<embed>`, or `<object>` blocks. This directive replaces the legacy `X-Frame-Options` header.

CSP Level 3 and strict-dynamic

As applications grow, whitelisting individual domains becomes brittle and hard to maintain. A modern approach introduced in CSP Level 3 is `strict-dynamic`. When combined with a cryptographic nonce or hash, `strict-dynamic` tells the browser: "Trust scripts with the valid nonce, and additionally trust any scripts dynamically loaded by those trusted scripts." This simplifies integration of analytics trackers or dynamic widgets (like YouTube widgets, Stripe checkouts, or Intercom chat widgets) while preventing unauthorized script injections.

Implementing Cryptographic Nonces in Next.js Middleware

To utilize nonces, the server must generate a unique, cryptographically random base64 string for every single HTTP response, inject it into the CSP header, and append matching attributes (`nonce="RANDOM_BASE64"`) to all script blocks in the HTML layout. Here is how you can write a Next.js edge middleware to generate and headers-pass CSP nonces:

// middleware.js example in Next.js for Dynamic CSP Nonces
import { NextResponse } from 'next/server';

export function middleware(request) {
  // Generate random crypto nonce
  const nonce = Buffer.from(crypto.randomUUID()).toString('base64');
  
  const cspHeader = `
    default-src 'self';
    script-src 'self' 'nonce-${nonce}' 'strict-dynamic';
    style-src 'self' 'unsafe-inline';
    img-src 'self' blob: data:;
    font-src 'self';
    object-src 'none';
    base-uri 'self';
    form-action 'self';
    frame-ancestors 'none';
  `.replace(/\s{2,}/g, ' ').trim();

  const requestHeaders = new Headers(request.headers);
  requestHeaders.set('x-nonce', nonce);
  requestHeaders.set('Content-Security-Policy', cspHeader);

  const response = NextResponse.next({
    request: {
      headers: requestHeaders,
    },
  });
  
  response.headers.set('Content-Security-Policy', cspHeader);
  return response;
}

Content-Security-Policy-Report-Only

Deploying a strict CSP directly into production can break dependencies. To prevent downtime, web administrators use the `Content-Security-Policy-Report-Only` header. This instructs the browser to monitor assets, compile warnings, and send logs to a specified reporting URI (`report-uri /api/csp-report`), but permits scripts to execute normally. Once audit logs are clean, the policy is promoted to a standard enforcing CSP.

B. HTTP Strict Transport Security (HSTS)

HTTP Strict Transport Security (HSTS) is a header that forces the browser to communicate with your domain exclusively over secure HTTPS channels.

When a user types a website address (e.g., `example.com`), the browser typically makes an unencrypted HTTP request first. The server then redirects the user to the secure HTTPS version. During this brief redirection window, the user is vulnerable to a Man-in-the-Middle (MITM) attack, such as an SSL Strip attack. An attacker sitting on the same local network can intercept the initial HTTP request, feed the user a duplicate unencrypted version of the website, and capture login tokens.

HSTS solves this problem. When a browser receives an HSTS header, it caches this preference. Any future attempt by the user to navigate to the site via HTTP is automatically rewritten to HTTPS client-side before any request goes over the wire.

Strict-Transport-Security: max-age=63072000; includeSubDomains; preload

Let us audit HSTS requirements:

  • max-age: The duration in seconds that the browser must remember to enforce secure connections. The standard value for production domains is `31536000` (1 year) or `63072000` (2 years). Any value less than 18 weeks (10886400 seconds) is flagged as insufficient by web diagnostics.
  • includeSubDomains: Applies the HSTS policy to all subdomains (e.g., `api.example.com` and `blog.example.com`). Caution: Ensure all subdomains support SSL before adding this!
  • preload: Submits the domain name to the global browser HSTS Preload list maintained by Google and adopted by all major browsers, ensuring the site is secure on the very first visit.

HSTS Preloading Requirements

For your domain to be eligible for preloading on `hstspreload.org`, it must fulfill strict conditions:

  1. Serve a valid SSL/TLS certificate.
  2. Redirect all HTTP requests on port 80 to HTTPS on port 443 on the same host (no cross-domain redirections).
  3. Serve HSTS headers on the base domain with a `max-age` of at least `18 weeks` (10886400 seconds).
  4. The HSTS header MUST include the `includeSubDomains` and `preload` directives.

C. X-Frame-Options

X-Frame-Options protects your users from Clickjacking attacks. A clickjacking attack occurs when an attacker overlays your genuine website page inside an invisible `<iframe>` on their malicious site.

When a user clicks on a seemingly harmless button on the malicious host, they are actually clicking on an invisible element on your website (e.g., a "Transfer Money" or "Delete Account" button).

X-Frame-Options: SAMEORIGIN

The options include `DENY` (no domain can frame the page) or `SAMEORIGIN` (only pages matching the exact same origin can frame it). While modern browsers prioritize CSP's `frame-ancestors` directive, `X-Frame-Options` must still be deployed for compatibility with legacy browsers.

D. X-Content-Type-Options

Web browsers automatically try to examine response payloads to guess their file types. This is known as MIME sniffing.

For example, if a server returns a `.txt` file containing Javascript code, but has a content type header of `text/plain`, the browser may sniff the content and execute it as JavaScript anyway. If an attacker uploaded a malicious script disguised as a profile picture upload, they could execute it in the browser context.

X-Content-Type-Options: nosniff

Enforcing the `nosniff` value blocks the browser from executing files whose content does not match the official `Content-Type` header returned by the server.

E. Referrer-Policy

When a user clicks a link to leave a website, the browser attaches a `Referer` header to the new request, revealing the URL of the origin page.

This URL may contain sensitive parameters, such as access tokens, account IDs, or private document paths. Referrer-Policy controls what referrer information is passed to external domains.

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

Deploying `strict-origin-when-cross-origin` keeps full page paths private during cross-origin jumps, sharing only the host domain name (e.g. `https://example.com/` instead of `https://example.com/account/dashboard?token=xyz`).

F. Permissions-Policy

Permissions-Policy (formerly known as Feature-Policy) restricts the availability of browser features and hardware APIs inside the current document and iframe children.

By restricting hardware access, you prevent cross-site scripts from invoking hardware components, such as a camera or location coordinates, without authorization.

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

The Directory of Features

Here is a checklist of critical client-side features you should restrict in your Permissions-Policy to enforce absolute sandboxing:

  • camera: Set `camera=()` to block accesses to device lenses.
  • microphone: Set `microphone=()` to disable microphone recordings.
  • geolocation: Set `geolocation=()` to prevent location tracking via browser location coordinates.
  • payment: Set `payment=()` to block child iframe billing requests.
  • usb: Set `usb=()` to block hardware connections to USB slots.
  • xr-spatial-tracking: Set `xr-spatial-tracking=()` to disable AR/VR device sensors.
  • interest-cohort: Set `interest-cohort=()` to opt-out of Google's cohort tracking analytics (FLoC) for privacy reasons.

3Configuration Guides: Hardening Multiple Server Environments

Response headers must be injected directly into the web server layer or application framework. Below are copy-pasteable configurations for standard infrastructure stacks.

1. Nginx Server Blocks

Add these directives within your `server` block configuration file (typically in `/etc/nginx/sites-available/default`):

# Inject Security Headers inside Nginx Configuration
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 Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self';" always;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;

2. Apache HTTP Server (`.htaccess` or `httpd.conf`)

Ensure that Apache's `mod_headers` module is enabled, then add this configuration to your root `.htaccess` file:

<IfModule mod_headers.c>
  # Inject security headers via Apache
  Header set X-Frame-Options "SAMEORIGIN"
  Header set X-Content-Type-Options "nosniff"
  Header set Referrer-Policy "strict-origin-when-cross-origin"
  Header set Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self';"
  Header set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
  Header set Permissions-Policy "camera=(), microphone=(), geolocation=()"
</IfModule>

3. Caddy Server Configuration

Add this configuration block inside your domain routing segment in the `Caddyfile`:

example.com {
    # Headers injection in Caddy
    header {
        X-Frame-Options "SAMEORIGIN"
        X-Content-Type-Options "nosniff"
        Referrer-Policy "strict-origin-when-cross-origin"
        Content-Security-Policy "default-src 'self'"
        Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
        Permissions-Policy "camera=(), microphone=(), geolocation=()"
    }
    reverse_proxy localhost:3000
}

4. Vercel Engagements (`vercel.json`)

If you host applications on Vercel, you can inject static response parameters in your `vercel.json` schema:

{
  "headers": [
    {
      "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": "Content-Security-Policy", "value": "default-src 'self';" },
        { "key": "Strict-Transport-Security", "value": "max-age=63072000; includeSubDomains; preload" },
        { "key": "Permissions-Policy", "value": "camera=(), microphone=(), geolocation=()" }
      ]
    }
  ]
}

5. Next.js Framework (via `next.config.js`)

In a Next.js App Router application, you can configure headers directly in `next.config.js`:

const securityHeaders = [
  { key: 'X-Frame-Options', value: 'SAMEORIGIN' },
  { key: 'X-Content-Type-Options', value: 'nosniff' },
  { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
  { key: 'Content-Security-Policy', value: "default-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline';" },
  { key: 'Strict-Transport-Security', value: 'max-age=63072000; includeSubDomains; preload' },
  { key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' }
];

module.exports = {
  async headers() {
    return [
      {
        source: '/:path*',
        headers: securityHeaders,
      },
    ];
  },
};

6. Netlify Configurations (`netlify.toml`)

If you use Netlify for hosting client portals, you can append header parameters directly in your project configuration file:

[[headers]]
  for = "/*"
  [headers.values]
    X-Frame-Options = "SAMEORIGIN"
    X-Content-Type-Options = "nosniff"
    Referrer-Policy = "strict-origin-when-cross-origin"
    Content-Security-Policy = "default-src 'self';"
    Strict-Transport-Security = "max-age=63072000; includeSubDomains; preload"
    Permissions-Policy = "camera=(), microphone=(), geolocation=()"

7. IIS Web Config (`web.config`)

For Windows Server and IIS environments, headers can be defined inside the system configuration file under the `customHeaders` tag:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <httpProtocol>
      <customHeaders>
        <add name="X-Frame-Options" value="SAMEORIGIN" />
        <add name="X-Content-Type-Options" value="nosniff" />
        <add name="Referrer-Policy" value="strict-origin-when-cross-origin" />
        <add name="Content-Security-Policy" value="default-src 'self';" />
        <add name="Strict-Transport-Security" value="max-age=63072000; includeSubDomains; preload" />
        <add name="Permissions-Policy" value="camera=(), microphone=(), geolocation=()" />
      </customHeaders>
    </httpProtocol>
  </system.webServer>
</configuration>

8. Cloudflare Workers (Edge Processing)

Injecting headers dynamically at the edge reduces database workloads on source hosts. Below is a JavaScript Cloudflare Worker configuration:

// Cloudflare Worker Script to inject headers
addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request))
});

async function handleRequest(request) {
  const response = await fetch(request);
  
  // Clone response to mutate headers
  const newResponse = new Response(response.body, response);
  
  newResponse.headers.set('X-Frame-Options', 'SAMEORIGIN');
  newResponse.headers.set('X-Content-Type-Options', 'nosniff');
  newResponse.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
  newResponse.headers.set('Content-Security-Policy', "default-src 'self';");
  newResponse.headers.set('Strict-Transport-Security', 'max-age=63072000; includeSubDomains; preload');
  newResponse.headers.set('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');
  
  return newResponse;
}

4Verification and Testing Methodologies

Once security headers are configured, they must be tested to ensure they are being parsed correctly by clients.

A. Terminal Testing with curl

Open your terminal and execute a `curl` lookup, extracting only the response headers. Replace `example.com` with your production URL:

$ curl -I https://example.com

In the output response parameters, verify that you see your configurations:

HTTP/2 200
server: nginx
date: Sat, 27 Jun 2026 03:00:00 GMT
content-type: text/html; charset=UTF-8
strict-transport-security: max-age=63072000; includeSubDomains; preload
x-frame-options: SAMEORIGIN
x-content-type-options: nosniff
referrer-policy: strict-origin-when-cross-origin
content-security-policy: default-src 'self';

B. Browser DevTools Network Audit

Open your browser, press `F12` or right-click and choose **Inspect**. Navigate to the **Network** tab, reload the page, and select the main document row. In the **Headers** details panel, scroll to the **Response Headers** section to confirm their values.

C. Automated Scanners

Submit your domain to public testing systems such as **SecurityHeaders.com** or the **Mozilla Observatory** to verify your grade. Deploying the headers in this handbook will instantly elevate your score to an A+ Grade.


5Real-World Agency Case Study: Hardening a Client Portal

To illustrate the value of HTTP security headers, let us consider a real-world scenario. A design agency, "Apex Digital," developed a customer dashboard for a major logistics client. During a security audit, the client's internal security team flagged the website for missing security headers, rating the site's posture as "High Risk."

Missing CSP, HSTS, and X-Frame-Options made the dashboard vulnerable to clickjacking overlays and DOM-based scripting injections.

Apex Digital followed a three-step remediation workflow:

  1. Establish Monitoring: Apex Digital configured the project inside their GuardianNode dashboard. The console immediately flagged 5 missing security headers, confirming the client's audit findings.
  2. Config Deployment: Apex Digital generated hardened header parameters matching their Nginx configuration, deployed them to their production block, and configured a dynamic nonce using Next.js middleware for script components.
  3. Resolution of Breakage: The initial CSP deployment blocked Google Analytics trackers and YouTube embeds. The developer whitelisted `https://www.google-analytics.com` inside the `connect-src` and `script-src` directives, and whitelisted `https://www.youtube.com` under `frame-src`.

After these edits, GuardianNode pings reported all headers as present. The client's audit tool updated the site's status to "Fully Compliant" with an A+ rating, strengthening Apex Digital's reputation as a secure agency partner.


6Common Deployment Pitfalls & Resolution Checklists

Configuring headers incorrectly can break application functions. Refer to this checklist to resolve common issues:

!

Issue: strict Content Security Policy breaks fonts or images

If you use Google Fonts or load assets from external CDNs, a strict `default-src 'self'` policy will block them. Check your browser's console tab for CSP violation notices. Update your `font-src` and `img-src` directives to whitelist those exact domains (e.g. `font-src 'self' https://fonts.gstatic.com`).

!

Issue: Strict HSTS locks out local development or staging

If you add `includeSubDomains` and run staging sites under HTTP (e.g., `http://staging.example.com`), the browser will redirect staging attempts to HTTPS, which will fail if staging lacks an SSL certificate. Run HSTS rules only on production domains or ensure all staging domains are HTTPS secured.

!

Issue: Duplicate header definitions

If you declare headers in both the web server config (like Nginx) and the application config (like Next.js), the server may return double headers. Some browsers reject duplicate CSP and HSTS headers. Ensure headers are configured at a single layer in your infrastructure stack.


7HTTP Security Headers FAQ

Q: Does Content Security Policy impact website loading speed?

No. CSP is a metadata response header processed by the browser layout engine. In fact, by preventing unauthorized third-party scripts from loading, a strict CSP can improve page load performance.

Q: What is the difference between X-Frame-Options SAMEORIGIN and DENY?

`SAMEORIGIN` permits other pages on your own website domain to frame the page. `DENY` blocks all websites (including your own) from framing the page. For login screens and account control dashboard panels, `DENY` is recommended.

Q: Can I deploy CSP in a <meta> tag instead of an HTTP response header?

Yes, you can declare CSP using `<meta http-equiv="Content-Security-Policy" content="...">`. However, some directives, such as `frame-ancestors` and reporting mechanisms, are not supported in HTML meta tags and must be sent as HTTP headers.

Q: What is HSTS Preloading?

HSTS preloading embeds your domain directly in the hardcoded security configuration list of modern browsers. This ensures that the browser will communicate with your site via HTTPS from the very first request, blocking even initial redirection vulnerabilities.