Security2025-01-0410 min read

Understanding HTTP Security Headers: Complete Guide

Learn about essential HTTP security headers that protect your website from attacks. Complete implementation guide with examples.

Introduction

HTTP security headers are a fundamental layer of web security. They instruct browsers on how to behave when handling your website's content, protecting against various attack vectors. This guide covers all essential security headers and how to implement them.

Why Security Headers Matter

Security headers protect against:

  • Cross-site scripting (XSS)
  • Clickjacking
  • MIME type sniffing
  • Man-in-the-middle attacks
  • Information leakage
  • Cross-site request forgery (CSRF)

Essential Security Headers

1. Strict-Transport-Security (HSTS)

Purpose: Enforces HTTPS connections and prevents protocol downgrade attacks.

Header Syntax:

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

Directives:

| Directive | Purpose | | ----------------- | -------------------------------------------- | | max-age | Time (seconds) browser should remember HTTPS | | includeSubDomains | Apply HSTS to all subdomains | | preload | Include in browser HSTS preload list |

Implementation:

# nginx
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Apache
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
// Express.js
app.use(
  helmet({
    hsts: {
      maxAge: 31536000,
      includeSubDomains: true,
      preload: true,
    },
  }),
);

Best Practice: Start with a short max-age (e.g., 300 seconds) and increase once confirmed HTTPS works properly.

2. Content-Security-Policy (CSP)

Purpose: Restricts sources for various content types, preventing XSS attacks.

Header Syntax:

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

Directives:

| Directive | Controls | | --------------- | -------------------------------- | | default-src | Default for all resource types | | script-src | JavaScript sources | | style-src | CSS sources | | img-src | Image sources | | font-src | Font sources | | connect-src | Fetch, XMLHttpRequest, WebSocket | | media-src | Audio and video | | object-src | Plugins (Flash, etc.) | | frame-src | Frame sources | | base-uri | Base URL | | form-action | Form target URLs | | frame-ancestors | Parents that may embed this page | | report-uri | CSP violation reporting endpoint |

Example Policies:

Strict (high security):

Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data: https:; object-src 'none'; base-uri 'self'; form-action 'self'

Moderate (allows CDN):

Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.trusted.com; style-src 'self' 'unsafe-inline' https://cdn.trusted.com; img-src 'self' data: https:;

Development Mode:

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

Implementation:

add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';" always;

Nonce-based CSP (more secure):

<!-- Server generates unique nonce -->
<script nonce="{random_nonce}">
  // Inline JavaScript
</script>
Content-Security-Policy: script-src 'self' 'nonce-{random_nonce}'

3. X-Frame-Options

Purpose: Prevents clickjacking by controlling who can embed your site.

Header Syntax:

X-Frame-Options: DENY

Values:

| Value | Meaning | | -------------- | -------------------------------------- | | DENY | No one can embed this page | | SAMEORIGIN | Only same origin can embed | | ALLOW-FROM uri | Specific origin can embed (deprecated) |

Note: CSP frame-ancestors is the modern replacement.

Implementation:

add_header X-Frame-Options "DENY" always;

CSP Equivalent:

Content-Security-Policy: frame-ancestors 'none';

4. X-Content-Type-Options

Purpose: Prevents MIME type sniffing, ensuring browsers respect the declared content type.

Header Syntax:

X-Content-Type-Options: nosniff

Implementation:

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

5. Referrer-Policy

Purpose: Controls how much referrer information is sent with navigations.

Header Syntax:

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

Values:

| Value | Behavior | | ------------------------------- | ------------------------------------------------------------------ | | no-referrer | No referrer sent | | no-referrer-when-downgrade | Full referrer for same-origin, none for HTTPS to HTTP | | origin | Only origin (no path) | | strict-origin | Origin only, not sent HTTPS to HTTP | | origin-when-cross-origin | Full URL for same-origin, origin for cross-origin | | strict-origin-when-cross-origin | Recommended - Full same-origin, origin for secure cross-origin | | unsafe-url | Full URL always (not recommended) |

Implementation:

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

6. Permissions-Policy (formerly Feature-Policy)

Purpose: Controls which browser features and APIs can be used.

Header Syntax:

Permissions-Policy: geolocation=(self), camera=(), microphone=(), payment=(self "https://payment.com")

Features to Control:

| Feature | Description | | --------------- | -------------------------------------- | | geolocation | Geolocation API | | camera | Camera access | | microphone | Microphone access | | payment | Payment Request API | | usb | USB devices | | magnetometer | Magnetometer API | | gyroscope | Gyroscope API | | fullscreen | Fullscreen API | | interest-cohort | FLoC (disable with interest-cohort=()) |

Implementation:

add_header Permissions-Policy "geolocation=(self), camera=(), microphone=(), payment=(self)" always;

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

Purpose: Isolates your window from other windows, preventing cross-origin attacks.

Header Syntax:

Cross-Origin-Opener-Policy: same-origin

Values:

  • unsafe-none: No isolation
  • same-origin: Isolates from cross-origin windows
  • same-origin-allow-popups: Allows popups to maintain opener relationship

8. Cross-Origin-Resource-Policy (CORP)

Purpose: Prevents cross-origin reads of resources.

Header Syntax:

Cross-Origin-Resource-Policy: same-origin

Values:

  • same-site: Only same-site can read
  • same-origin: Only same-origin can read
  • cross-origin: Any origin can read

9. Cross-Origin-Embedder-Policy (COEP)

Purpose: Requires CORP on cross-origin resources, enabling advanced features like SharedArrayBuffer.

Header Syntax:

Cross-Origin-Embedder-Policy: require-corp

Note: COEP, COOP, and CORP are often used together for advanced security isolation.

Security Headers Implementation

Complete nginx Configuration

server {
    # HSTS
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

    # CSP
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none';" always;

    # Frame protection
    add_header X-Frame-Options "DENY" always;

    # MIME sniffing
    add_header X-Content-Type-Options "nosniff" always;

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

    # Permissions policy
    add_header Permissions-Policy "geolocation=(self), camera=(), microphone=(), payment=(self), usb=(), interest-cohort=()" always;

    # Other security headers
    add_header X-XSS-Protection "1; mode=block" always;
}

Complete Apache Configuration

# Enable headers module
LoadModule headers_module modules/mod_headers.so

<VirtualHost *:443>
    ServerName example.com

    # HSTS
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"

    # CSP
    Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; object-src 'none'"

    # Frame protection
    Header always set X-Frame-Options "DENY"

    # MIME sniffing
    Header always set X-Content-Type-Options "nosniff"

    # Referrer policy
    Header always set Referrer-Policy "strict-origin-when-cross-origin"

    # Permissions policy
    Header always set Permissions-Policy "geolocation=(self), camera=(), microphone=()"

    # XSS protection (legacy)
    Header always set X-XSS-Protection "1; mode=block"
</VirtualHost>

Express.js with Helmet

const express = require("express");
const helmet = require("helmet");

const app = express();

// Basic helmet (recommended defaults)
app.use(helmet());

// Custom configuration
app.use(
  helmet({
    contentSecurityPolicy: {
      directives: {
        defaultSrc: ["'self'"],
        scriptSrc: ["'self'", "'unsafe-inline'"],
        styleSrc: ["'self'", "'unsafe-inline'"],
        imgSrc: ["'self'", "data:", "https:"],
        objectSrc: ["'none'"],
        baseUri: ["'self'"],
        formAction: ["'self'"],
        frameAncestors: ["'none'"],
      },
    },
    hsts: {
      maxAge: 31536000,
      includeSubDomains: true,
      preload: true,
    },
    referrerPolicy: { policy: "strict-origin-when-cross-origin" },
    permissionsPolicy: {
      features: {
        geolocation: ["'self'"],
        camera: ["'none'"],
        microphone: ["'none'"],
      },
    },
  }),
);

Next.js Configuration

// next.config.js
module.exports = {
  async headers() {
    return [
      {
        source: "/(.*)",
        headers: [
          {
            key: "Strict-Transport-Security",
            value: "max-age=31536000; includeSubDomains; preload",
          },
          {
            key: "X-Frame-Options",
            value: "DENY",
          },
          {
            key: "X-Content-Type-Options",
            value: "nosniff",
          },
          {
            key: "Referrer-Policy",
            value: "strict-origin-when-cross-origin",
          },
          {
            key: "Permissions-Policy",
            value: "geolocation=(self), camera=(), microphone=()",
          },
          {
            key: "Content-Security-Policy",
            value:
              "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none';",
          },
        ],
      },
    ];
  },
};

Testing Security Headers

1. Browser DevTools

// Check security headers in console
fetch(window.location.href).then((response) => {
  console.log("CSP:", response.headers.get("Content-Security-Policy"));
  console.log("HSTS:", response.headers.get("Strict-Transport-Security"));
  console.log("X-Frame-Options:", response.headers.get("X-Frame-Options"));
});

2. Online Tools

  • Security Headers: https://securityheaders.com/
  • Mozilla Observatory: https://observatory.mozilla.org/
  • WebScope: Run our security scanner

3. Command Line

curl -I https://example.com

Common CSP Violations and Fixes

1. Inline Scripts Blocked

Error:

Refused to execute inline script because it violates the Content-Security-Policy

Fix: Use nonce or hash

<script nonce="random_value">
  ...
</script>
Content-Security-Policy: script-src 'self' 'nonce-random_value'

2. External Font Blocked

Fix: Add to CSP

Content-Security-Policy: font-src 'self' https://fonts.gstatic.com

3. eval() Blocked

Fix: Remove eval or add 'unsafe-eval' (not recommended)

Content-Security-Policy: script-src 'self' 'unsafe-eval'

Security Headers Checklist

Use this checklist to verify your security headers:

Essential
  [ ] Strict-Transport-Security (max-age >= 31536000)
  [ ] X-Frame-Options (DENY or SAMEORIGIN)
  [ ] X-Content-Type-Options (nosniff)
  [ ] Content-Security-Policy (default-src configured)

Recommended
  [ ] Referrer-Policy (strict-origin-when-cross-origin)
  [ ] Permissions-Policy (features restricted as needed)
  [ ] Cross-Origin-Opener-Policy (same-origin)

Context-Dependent
  [ ] Cross-Origin-Resource-Policy (for specific resources)
  [ ] Cross-Origin-Embedder-Policy (when using SharedArrayBuffer)

Conclusion

Security headers are a critical defense layer for any website. Implement all essential headers first, then add context-specific ones based on your needs. Regular testing ensures your headers work as expected.

Run a security scan to check your website's security headers and get specific recommendations for improvement.

Scan Your Website Now

Get comprehensive insights into your website's technology, security, SEO, and performance.

You Might Also Like