Security Guide

HTTP Headers Analyzer Complete Guide: Security Headers, Caching, CORS & Performance (2025)

42 min read
5862 words
Share:

HTTP Headers Analyzer Complete Guide: Security Headers, Caching, CORS & Performance (2025)

The Problem: One Misconfigured HTTP Header Can Compromise Your Entire Application

3:47 AM. Your security team receives an alert. Attackers are exploiting a clickjacking vulnerability on your production banking application. Users’ sessions are being hijacked through iframe embedding on malicious sites. The culprit? A missing X-Frame-Options header.

You frantically check other security headers. No Content-Security-Policy. No Strict-Transport-Security. Your Server header proudly announces Apache/2.4.29 (Ubuntu) — giving attackers a complete roadmap to exploit 47 known CVEs in that exact version.

Your CDN is serving stale content for critical user data because someone set Cache-Control: max-age=86400 on a dynamic API endpoint. CORS is misconfigured with Access-Control-Allow-Origin: * alongside Access-Control-Allow-Credentials: true, creating a gaping security hole.

This isn’t hypothetical. The 2023 Verizon Data Breach Investigations Report found that 43% of web application attacks exploit missing or misconfigured HTTP security headers. OWASP lists “Security Misconfiguration” in its Top 10 Web Application Security Risks, with improper HTTP headers as a primary attack vector.

The irony? These vulnerabilities are completely preventable. Every single one could have been caught with proper HTTP header analysis.


Quick Answer: What Are HTTP Headers and Why Analyze Them?

HTTP headers are metadata key-value pairs transmitted in HTTP requests and responses following RFC 9110 (HTTP Semantics). Response headers control:

  • Security policies (CSP, HSTS, X-Frame-Options) preventing XSS, clickjacking, SSL stripping
  • Caching behavior per RFC 9111 reducing latency and bandwidth costs
  • CORS policies enabling cross-origin resource sharing under WHATWG Fetch Standard
  • Content negotiation for compression (Brotli, gzip), language, encoding
  • Server information that can expose vulnerability attack surfaces

HTTP header analysis examines these response headers to identify:

  • Missing OWASP Secure Headers (CSP, HSTS, X-Content-Type-Options)
  • Information disclosure vulnerabilities (CWE-200) via Server, X-Powered-By headers
  • Misconfigured caching causing stale content or excessive origin requests
  • Overly permissive CORS (CWE-942) enabling data theft
  • HTTP/2 and HTTP/3 optimization opportunities via HPACK/QPACK compression

Use our HTTP Headers Analyzer to instantly audit any website’s security posture, caching configuration, and compliance with PCI DSS, OWASP WSTG, and GDPR requirements. Test alongside SSL/TLS Certificate validation and IP geolocation analysis for complete infrastructure security assessment.


Part 1: HTTP Headers Fundamentals and Protocol Standards

Understanding HTTP Response Headers (RFC 9110)

HTTP headers were standardized in RFC 2616 (1999) for HTTP/1.1, then revised in RFC 7230-7235 (2014), and most recently consolidated in RFC 9110 (June 2022) as part of the HTTP Semantics specification.

Response headers provide metadata about the server’s response, separate from the response body:

HTTP/1.1 200 OK
Date: Mon, 25 Nov 2025 12:00:00 GMT
Server: nginx/1.24.0
Content-Type: application/json; charset=utf-8
Content-Length: 348
Cache-Control: no-cache, no-store, must-revalidate
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'
X-Frame-Options: DENY
X-Content-Type-Options: nosniff

{"status":"success","data":{...}}

Header anatomy:

  • Field name: Case-insensitive identifier (e.g., Content-Type, cache-control)
  • Field value: Directive or metadata following the colon
  • Separator: Colon and optional whitespace (: )
  • Terminator: CRLF (\r\n) line ending per RFC 9112 Section 2.2

HTTP Protocol Evolution: HTTP/1.1 vs HTTP/2 vs HTTP/3

Feature HTTP/1.1 (RFC 9112) HTTP/2 (RFC 9113) HTTP/3 (RFC 9114)
Transport TCP TCP QUIC (UDP)
Header format Plain text Binary (HPACK compressed) Binary (QPACK compressed)
Multiplexing No (6 connections) Yes (single connection) Yes + out-of-order delivery
Header compression None HPACK (RFC 7541) 85-95% QPACK (RFC 9204) 90%+
Connection overhead High (per-request TCP) Medium (single TCP) Low (0-RTT resumption)
Head-of-line blocking Yes (TCP level) Yes (TCP level) No (QUIC streams)

Impact on header analysis:

  • HTTP/2 reduces header overhead dramatically via HPACK static/dynamic table compression
  • HTTP/3 over QUIC enables faster connection establishment (0-RTT with session tickets)
  • Legacy systems still use HTTP/1.1, requiring plain-text header parsing
  • All protocols share same header semantics (RFC 9110), just different wire formats

Header size considerations:

  • HTTP/1.1: Average 400-800 bytes per request/response (uncompressed)
  • HTTP/2 with HPACK: 50-150 bytes (85-90% compression on repeated headers)
  • HTTP/3 with QPACK: 40-120 bytes (better handling of dynamic tables)

Testing HTTP version and headers:

# Check HTTP version and headers
curl -I --http2 https://example.com

# Force HTTP/1.1
curl -I --http1.1 https://example.com

# Test HTTP/3 support (requires curl 7.88+)
curl -I --http3 https://cloudflare.com

Standards compliance: Modern applications should support HTTP/2 minimum (95% browser support as of 2025), with HTTP/3 deployment growing (78% of top 10M sites per W3Techs). Validate protocol support with our HTTP Status Checker.


Part 2: OWASP Security Headers - Critical Protection Mechanisms

Content-Security-Policy: XSS and Code Injection Defense

Content-Security-Policy (CSP), defined by W3C CSP Level 3, is the most powerful HTTP security header for preventing Cross-Site Scripting (XSS) attacks classified as CWE-79.

CSP mechanism: Whitelists approved content sources, blocking all unauthorized resources. Browsers reject inline scripts, eval(), and external resources not matching CSP directives.

Core CSP directives:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://cdn.example.com;
  style-src 'self' 'unsafe-inline';
  img-src 'self' data: https:;
  font-src 'self' https://fonts.gstatic.com;
  connect-src 'self' https://api.example.com;
  frame-ancestors 'none';
  base-uri 'self';
  form-action 'self';
  upgrade-insecure-requests;
  block-all-mixed-content;
  report-uri https://csp-reports.example.com/report

CSP directive reference:

Directive Purpose Example Value Security Impact
default-src Fallback for all resources 'self' Blocks all external origins by default
script-src JavaScript sources 'self' https://cdn.com Prevents inline/eval XSS exploitation
style-src CSS stylesheet sources 'self' 'unsafe-inline' Controls CSS injection attacks
img-src Image sources 'self' data: https: Prevents image-based tracking/exploits
connect-src AJAX/WebSocket endpoints 'self' https://api.com Limits data exfiltration targets
font-src Web font sources https://fonts.gstatic.com Prevents font-based fingerprinting
frame-src Iframe sources 'none' Prevents loading malicious iframes
frame-ancestors Embedding restrictions 'none' Replaces X-Frame-Options (clickjacking)
object-src Plugin content (Flash) 'none' Disables legacy plugin exploits
base-uri Document base URL 'self' Prevents base tag hijacking
form-action Form submission targets 'self' Prevents CSRF via form manipulation

CSP source expression syntax:

  • 'self' — Same origin only (https://example.com)
  • 'none' — Block all sources
  • 'unsafe-inline' — Allow inline scripts/styles (avoid in production)
  • 'unsafe-eval' — Allow eval() and related functions (dangerous)
  • https: — Any HTTPS origin
  • data: — Data URIs (images as base64)
  • 'nonce-r4nd0m' — Cryptographic nonce for specific inline scripts
  • 'sha256-hash' — SHA-256 hash of allowed inline content
  • https://example.com — Specific origin whitelist

CSP deployment strategy (phased rollout):

Phase 1: Report-Only Mode

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

Deploy in monitoring mode to collect violations without blocking. Analyze violation reports for 2-4 weeks to identify legitimate third-party dependencies.

Phase 2: Permissive Enforcement

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

Start with permissive policy allowing common patterns. Monitor error rates and user impact.

Phase 3: Strict Policy

Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-{random}'; style-src 'self'

Remove 'unsafe-inline' by implementing nonces or hashes. Achieve maximum XSS protection.

Real-world CSP example (GitHub):

Content-Security-Policy:
  default-src 'none';
  base-uri 'self';
  child-src github.com/assets-cdn/worker/ gist.github.com/assets-cdn/worker/;
  connect-src 'self' uploads.github.com objects-origin.githubusercontent.com;
  font-src github.githubassets.com;
  form-action 'self' github.com gist.github.com;
  frame-ancestors 'none';
  frame-src viewscreen.githubusercontent.com notebooks.githubusercontent.com;
  img-src 'self' data: github.githubassets.com identicons.github.com;
  script-src github.githubassets.com;
  style-src 'unsafe-inline' github.githubassets.com;
  upgrade-insecure-requests

CSP violation reporting: Configure report-uri or newer report-to directive to receive JSON violation reports:

{
  "csp-report": {
    "document-uri": "https://example.com/page",
    "referrer": "https://example.com/",
    "violated-directive": "script-src 'self'",
    "effective-directive": "script-src",
    "original-policy": "default-src 'self'; script-src 'self'",
    "blocked-uri": "https://evil.com/malicious.js",
    "status-code": 200,
    "script-sample": "try{evil();}catch..."
  }
}

Testing CSP configuration:

  • Browser DevTools Console shows CSP violations with detailed explanations
  • CSP Evaluator by Google security team
  • Report URI CSP Builder for policy generation
  • Manual testing: curl -I https://example.com | grep -i content-security

Common CSP mistakes:

  1. Using 'unsafe-inline' defeats XSS protection (use nonces instead)
  2. Overly broad wildcards like https://* (specify exact origins)
  3. Missing frame-ancestors (still vulnerable to clickjacking)
  4. Not monitoring violation reports (blind to policy effectiveness)
  5. Setting CSP only on HTML (also set on JSON/XML API responses)

Strict-Transport-Security (HSTS): TLS Enforcement

HTTP Strict Transport Security (HSTS), specified in RFC 6797, forces browsers to use HTTPS exclusively, preventing SSL stripping attacks and protocol downgrade vulnerabilities.

HSTS mechanism: After receiving HSTS header, browsers automatically upgrade all HTTP requests to HTTPS for the specified duration, rejecting invalid certificates without user override.

HSTS header syntax:

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

HSTS directives:

Directive Purpose Recommended Value Security Impact
max-age Cache duration (seconds) 31536000 (1 year minimum) Time browser enforces HTTPS
includeSubDomains Apply to all subdomains Always include Protects *.example.com
preload Browser preload list eligibility Include after testing Protection on first visit

Why HSTS matters (SSL stripping attack prevention):

Without HSTS:

  1. User types example.com → Browser sends HTTP request
  2. Attacker intercepts, proxies HTTPS to server, serves HTTP to user
  3. User sees HTTP, session hijacked, credentials stolen
  4. Server redirects HTTP → HTTPS but attacker already has session

With HSTS:

  1. User types example.com → Browser internally upgrades to HTTPS
  2. Bypass attacker’s MitM proxy, establish direct TLS connection
  3. Invalid certificate rejected, connection terminated
  4. User protected without clicking through certificate warnings

HSTS Preload List: hstspreload.org maintains a hardcoded list of HSTS domains shipped in Chrome, Firefox, Safari, Edge. Submit your domain after:

Preload eligibility requirements:

  1. Valid HTTPS certificate on all subdomains
  2. Redirect HTTP → HTTPS on base domain
  3. Serve HSTS header on base domain with:
    • max-age ≥ 31536000 (1 year)
    • includeSubDomains directive
    • preload directive
  4. All subdomains must support HTTPS (including localhost subdomains in development!)

HSTS deployment workflow:

Step 1: Test with short max-age

Strict-Transport-Security: max-age=300

Deploy for 5 minutes, verify no HTTP dependencies break functionality.

Step 2: Extend to subdomains

Strict-Transport-Security: max-age=86400; includeSubDomains

Test for 1 day across all subdomains. Check API endpoints, CDN assets, internal tools.

Step 3: Production HSTS (1 year)

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

Deploy maximum duration. Monitor error rates for 2-4 weeks.

Step 4: Submit preload (optional)

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

2-year max-age recommended for preload submission. Submit at hstspreload.org.

HSTS removal (escape hatch):
If HSTS breaks production (rare), send max-age=0 to clear browser cache:

Strict-Transport-Security: max-age=0

Users must visit site to receive cancellation header. Preload removal takes 6-12 weeks via removal form.

Testing HSTS:

# Check HSTS header presence
curl -I https://example.com | grep -i strict-transport-security

# Verify HSTS preload status
curl -s https://hstspreload.org/api/v2/status?domain=example.com | jq

# Browser DevTools Security tab shows HSTS status
# Chrome: chrome://net-internals/#hsts

HSTS with our tools:

X-Frame-Options: Clickjacking Protection

X-Frame-Options prevents clickjacking attacks (CWE-1021) by controlling iframe embedding. While superseded by CSP frame-ancestors, X-Frame-Options remains critical for legacy browser support (IE11, older Safari).

Clickjacking attack scenario:

  1. Attacker embeds your site in invisible iframe over malicious page
  2. User thinks they’re clicking attacker’s button, actually clicking your “Transfer Funds” button
  3. Transparent iframe positioned precisely to capture clicks
  4. User unknowingly authorizes transactions, changes settings, deletes data

X-Frame-Options directives:

X-Frame-Options: DENY

Never allow iframe embedding (most secure for authentication pages).

X-Frame-Options: SAMEORIGIN

Allow embedding only on same origin (example.com can iframe example.com/page).

X-Frame-Options: ALLOW-FROM https://trusted.com

Obsolete directive (removed from standards). Use CSP frame-ancestors instead.

Modern alternative (CSP frame-ancestors):

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

Equivalent to X-Frame-Options: DENY with better browser support.

Content-Security-Policy: frame-ancestors 'self' https://partner.com

More flexible than X-Frame-Options, allows multiple trusted origins.

Deployment recommendation:
Deploy both headers for maximum compatibility:

X-Frame-Options: DENY
Content-Security-Policy: frame-ancestors 'none'

When to allow framing:

  • Embedded widgets (analytics dashboards, payment forms)
  • OAuth/SAML authentication flows requiring iframe
  • Partner integrations with iframe-based UIs

Use SAMEORIGIN for these cases:

X-Frame-Options: SAMEORIGIN
Content-Security-Policy: frame-ancestors 'self' https://partner1.com https://partner2.com

X-Content-Type-Options: MIME Sniffing Prevention

X-Content-Type-Options prevents browsers from MIME-sniffing responses away from declared Content-Type, blocking a subtle XSS vector.

MIME-sniffing attack:

  1. Attacker uploads “image” containing HTML/JavaScript disguised as image
  2. Server sets Content-Type: image/png based on file extension
  3. Browser sniffs content, detects HTML, renders as HTML page
  4. JavaScript executes in victim’s session context (XSS achieved)

Protection header:

X-Content-Type-Options: nosniff

Only valid value is nosniff. Apply to all responses, especially user-uploaded content.

Critical for:

  • File upload endpoints (images, PDFs, documents)
  • API responses (JSON, XML)
  • Static asset servers (CSS, JavaScript)
  • Download endpoints (Content-Disposition: attachment)

MIME type validation:
Combine with strict Content-Type headers:

Content-Type: application/json; charset=utf-8
X-Content-Type-Options: nosniff

Browser will reject JSON served as text/html, preventing type confusion attacks.

Server configuration examples:

Nginx:

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

Apache:

Header always set X-Content-Type-Options "nosniff"

Express.js:

app.use((req, res, next) => {
  res.setHeader('X-Content-Type-Options', 'nosniff');
  next();
});

Referrer-Policy: Information Disclosure Control

Referrer-Policy controls referrer information sent in Referer header (note: misspelled in HTTP spec since 1996), preventing CWE-200 information disclosure via URL parameters.

Information leakage via referer:

  • Session tokens in query strings: https://app.com/dashboard?token=secret123
  • Sensitive search queries: https://medical.com/search?q=embarrassing+condition
  • User tracking across sites via URL parameters
  • Internal infrastructure details (API endpoints, server names)

Referrer-Policy directives:

Policy Referrer Sent Use Case Privacy Level
no-referrer Never Maximum privacy High
no-referrer-when-downgrade Only HTTPS→HTTPS Default browser behavior Medium
origin Origin only (no path) Balance privacy/analytics Medium-High
origin-when-cross-origin Full URL same-origin, origin only cross-origin Recommended general use Medium
same-origin Only same-origin requests Strict privacy High
strict-origin Origin only, never HTTPS→HTTP HTTPS-only sites High
strict-origin-when-cross-origin Full same-origin, origin HTTPS cross-origin Best practice (default in modern browsers) Medium-High
unsafe-url Always full URL (never use) Backwards compatibility only Low

Recommended configuration:

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

Modern browser default (Chrome 85+, Firefox 87+). Balances analytics needs with privacy.

For maximum privacy (sensitive applications):

Referrer-Policy: no-referrer

Banking, healthcare, legal applications should block all referrer information.

HTML meta tag alternative:

<meta name="referrer" content="strict-origin-when-cross-origin">

Per-link override:

<a href="https://external.com" referrerpolicy="no-referrer">External Link</a>

Testing referrer policy:

# Check server-side referrer policy
curl -I https://example.com | grep -i referrer-policy

# Client-side testing
# Navigate to https://example.com → click link → check document.referrer in console

Permissions-Policy: Feature Access Control

Permissions-Policy (formerly Feature-Policy) restricts browser API access, preventing malicious iframes from accessing camera, microphone, geolocation, and other sensitive features.

Permissions-Policy syntax:

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

Common directives:

Feature Default Recommended Setting Threat Model
camera * (all) () (none) Prevent webcam spying via malicious ads
microphone * () Block audio recording/eavesdropping
geolocation * (self) Limit location tracking
payment * (self) or specific origins Prevent unauthorized payment requests
usb * () Block USB device access
interest-cohort * () Opt out of FLoC tracking (privacy)
autoplay * (self) Control video autoplay behavior
fullscreen * (self) Prevent fullscreen hijacking
picture-in-picture * (self) Control PiP video feature
accelerometer * () Block motion sensor access
gyroscope * () Prevent orientation tracking

Restrictive default policy:

Permissions-Policy:
  camera=(),
  microphone=(),
  geolocation=(),
  payment=(),
  usb=(),
  interest-cohort=(),
  accelerometer=(),
  gyroscope=(),
  magnetometer=()

Selectively enable for trusted origins:

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

Testing Permissions-Policy:

// Check if feature is allowed
navigator.permissions.query({name: 'camera'}).then(result => {
  console.log('Camera permission:', result.state);
});

// Browser DevTools Console shows policy violations

Cross-Origin-Opener-Policy & Cross-Origin-Embedder-Policy

COOP/COEP headers provide process isolation protecting against Spectre/Meltdown side-channel attacks by restricting cross-origin window access and resource loading.

Cross-Origin-Opener-Policy (COOP):

Cross-Origin-Opener-Policy: same-origin

Values:

  • unsafe-none — Default, allows cross-origin window access
  • same-origin-allow-popups — Isolate except popups opened by document
  • same-origin — Strict isolation, separates browsing context

Cross-Origin-Embedder-Policy (COEP):

Cross-Origin-Embedder-Policy: require-corp

Values:

  • unsafe-none — Default, no restrictions
  • require-corp — All subresources must have CORS or Cross-Origin-Resource-Policy

Combined deployment for maximum isolation:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Resource-Policy: same-origin

Required for SharedArrayBuffer and high-precision timers:

// Only available with COOP + COEP
const buffer = new SharedArrayBuffer(1024);
const timestamp = performance.now(); // High precision (5μs instead of 100μs)

Testing cross-origin isolation:

// Check if cross-origin isolated
console.log(self.crossOriginIsolated); // true if COOP + COEP enabled

Part 3: HTTP Caching Headers (RFC 9111) - Performance Optimization

Cache-Control: Modern Caching Directives

Cache-Control, defined in RFC 9111 Section 5.2, replaced legacy Expires header with granular caching control. Proper caching reduces bandwidth costs 60-90%, improves Time to First Byte (TTFB), and decreases server load.

Cache-Control directive syntax:

Cache-Control: max-age=31536000, public, immutable

Core caching directives:

Directive Scope Meaning Use Case
max-age=N Shared + Private Cache duration in seconds All cacheable resources
s-maxage=N Shared only Override max-age for CDN/proxy CDN-specific TTL
public Shared + Private Cacheable by all intermediaries Static assets, public APIs
private Browser only Only user’s browser can cache Personalized content, auth responses
no-cache Validation required Revalidate with origin via ETag/Last-Modified HTML pages, dynamic content
no-store Never cache Don’t cache anywhere (RAM/disk) Banking, PII, sessions
must-revalidate Strict validation Never serve stale without revalidation Critical business data
immutable No revalidation Resource never changes Content-hashed filenames
stale-while-revalidate=N Serve stale while fetching Serve cached copy, async refresh Balance freshness/performance
stale-if-error=N Error fallback Serve stale if origin returns error Resilience during outages

Caching strategy by resource type:

1. Static immutable assets (JS, CSS, images with content hashes):

Cache-Control: max-age=31536000, immutable, public
  • Files: app.a3f8d2.js, styles.9c4e1f.css
  • Safe because filename changes when content changes
  • Browsers skip revalidation even on reload (performance boost)
  • 1 year maximum recommended by HTTP spec

2. HTML pages (needs freshness):

Cache-Control: no-cache, public
  • Always revalidate with origin (sends If-None-Match with ETag)
  • Returns 304 Not Modified if unchanged (saves bandwidth)
  • Ensures users get updated content immediately

3. API responses (authenticated, personalized):

Cache-Control: private, max-age=60
  • Only user’s browser caches (not CDN)
  • 60 second TTL for dashboard data, notifications
  • Use no-store for truly sensitive data (passwords, tokens)

4. Sensitive data (banking, PII):

Cache-Control: no-store, no-cache, must-revalidate, private
  • Never cached anywhere (not even browser back button)
  • GDPR/PCI DSS compliance requirement
  • Essential for logout functionality security

5. CDN-cached public content with different TTLs:

Cache-Control: public, max-age=300, s-maxage=86400
  • Browser caches 5 minutes (max-age=300)
  • CDN caches 24 hours (s-maxage=86400)
  • Useful for news sites, e-commerce product pages

6. Stale-while-revalidate optimization:

Cache-Control: max-age=3600, stale-while-revalidate=86400
  • Fresh for 1 hour
  • After expiry, serve stale copy while fetching fresh in background
  • User sees instant response, next user gets fresh content
  • Excellent for resilience during origin outages

Cache-Control precedence:
When multiple directives conflict, specificity wins:

  • s-maxage overrides max-age for shared caches
  • no-store overrides everything (never cache)
  • private overrides public
  • immutable prevents revalidation even if max-age expired

ETag: Conditional Request Validation

ETag (Entity Tag), specified in RFC 9110 Section 8.8.3, enables efficient cache revalidation via strong or weak validators.

ETag workflow (conditional GET):

Initial request:

GET /api/user/profile HTTP/1.1
Host: example.com

HTTP/1.1 200 OK
ETag: "33a64df551425fcc55e"
Cache-Control: no-cache
Content-Type: application/json

{"name":"Alice","email":"alice@example.com"}

Subsequent request (conditional):

GET /api/user/profile HTTP/1.1
Host: example.com
If-None-Match: "33a64df551425fcc55e"

HTTP/1.1 304 Not Modified
ETag: "33a64df551425fcc55e"

Bandwidth savings: 304 response typically 200-300 bytes vs full response (could be 50KB+).

ETag types:

Strong ETag (byte-perfect match):

ETag: "686897696a7c876b7e"
  • Cryptographic hash (MD5, SHA-256) or revision number
  • Guarantees identical byte representation
  • Required for Range requests (video streaming, file downloads)

Weak ETag (semantic equivalence):

ETag: W/"686897696a7c876b7e"
  • Prefixed with W/
  • Content semantically equivalent but not byte-identical
  • Example: gzip vs uncompressed same resource
  • Acceptable for cache validation, not Range requests

ETag generation strategies:

1. Content hash (cryptographic):

// Node.js example
const crypto = require('crypto');
const etag = crypto.createHash('md5').update(content).digest('hex');
response.setHeader('ETag', `"${etag}"`);

2. Modification timestamp + size:

const etag = `"${file.mtime.getTime()}-${file.size}"`;

3. Database version column:

SELECT version FROM articles WHERE id = 123;
-- version column auto-incremented on UPDATE

If-None-Match vs If-Match:

If-None-Match (cache validation):

If-None-Match: "abc123", W/"def456"

Returns 304 Not Modified if ETag matches any in list.

If-Match (conditional updates, preventing lost updates):

PUT /api/article/123 HTTP/1.1
If-Match: "abc123"
Content-Type: application/json

{"title":"Updated Title"}

Returns 412 Precondition Failed if ETag doesn’t match (concurrent edit detected).

ETag best practices:

  • Generate strong ETags for static assets (content hashes)
  • Use weak ETags for dynamic content with semantic versioning
  • Combine with Cache-Control: no-cache for validation-based caching
  • Essential for APIs supporting concurrent updates (preventing race conditions)

Last-Modified and Conditional Requests

Last-Modified, defined in RFC 9110 Section 8.8.2, provides timestamp-based cache validation, predecessor to ETags.

Last-Modified workflow:

Initial response:

HTTP/1.1 200 OK
Last-Modified: Wed, 21 Oct 2024 07:28:00 GMT
Cache-Control: no-cache

Conditional request:

GET /document.pdf HTTP/1.1
If-Modified-Since: Wed, 21 Oct 2024 07:28:00 GMT

HTTP/1.1 304 Not Modified
Last-Modified: Wed, 21 Oct 2024 07:28:00 GMT

Limitations of Last-Modified:

  1. 1-second precision — Files modified multiple times per second appear unchanged
  2. Clock synchronization — Server time drift causes stale content
  3. File restoration — Restoring from backup changes mtime but not content
  4. CDN clock skew — Origin and edge servers may disagree on timestamps

ETag advantages over Last-Modified:

  • Sub-second granularity
  • Content-based (hash), not time-based
  • Works for generated content without filesystem modification time
  • No clock synchronization issues

Prefer ETags, fallback to Last-Modified:

ETag: "abc123"
Last-Modified: Wed, 21 Oct 2024 07:28:00 GMT

Browsers send both conditional headers if available:

If-None-Match: "abc123"
If-Modified-Since: Wed, 21 Oct 2024 07:28:00 GMT

Server checks ETag first (more accurate), falls back to timestamp.

Vary: Cache Key Diversity

Vary header, per RFC 9110 Section 12.5.5, instructs caches to maintain separate cached copies based on request header values.

Common Vary patterns:

1. Content negotiation (compression):

Vary: Accept-Encoding

CDN stores separate copies for:

  • Uncompressed response (no Accept-Encoding)
  • Gzip compressed (Accept-Encoding: gzip)
  • Brotli compressed (Accept-Encoding: br)

2. CORS with dynamic origins:

Vary: Origin
Access-Control-Allow-Origin: https://app1.example.com

Prevents cache poisoning where one origin’s CORS response served to different origin.

3. User-Agent based responses:

Vary: User-Agent

Separate mobile vs desktop cached copies. Avoid when possible — creates excessive cache fragmentation.

4. Multiple factors:

Vary: Accept-Encoding, Origin

Cache key formula with Vary:

cache_key = URL + Vary_header_values

Example:

  • URL: https://cdn.com/app.js
  • Vary: Accept-Encoding
  • Cache keys:
    • https://cdn.com/app.js|gzip (for gzip requests)
    • https://cdn.com/app.js|br (for Brotli requests)
    • https://cdn.com/app.js|identity (uncompressed)

Vary pitfalls:

  • High cardinality headers (Cookie, User-Agent) cause cache fragmentation
  • Each unique combination creates separate cache entry
  • Reduces cache hit rate dramatically
  • CDN costs increase with poor Vary configuration

Best practice:
Use Vary sparingly. For personalized content, use Cache-Control: private instead of Vary: Cookie.


Part 4: CORS Headers and Cross-Origin Security

Understanding Cross-Origin Resource Sharing (CORS)

CORS, specified by WHATWG Fetch Standard, relaxes Same-Origin Policy (SOP) defined in RFC 6454 to enable controlled cross-domain AJAX requests.

Same-Origin Policy refresher:

  • Origin = Protocol + Domain + Port
  • https://example.com:443http://example.com:80 (different protocol)
  • https://api.example.comhttps://example.com (different subdomain)
  • JavaScript from https://app.com cannot read responses from https://api.other.com by default

CORS mechanism:
Browsers send Origin header with cross-origin requests:

GET /api/data HTTP/1.1
Host: api.example.com
Origin: https://app.example.com

Server responds with Access-Control-Allow-Origin:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Content-Type: application/json

{"data":"value"}

Browser checks CORS headers, permits JavaScript access if origin allowed.

Simple vs Preflighted CORS Requests

Simple requests (no preflight):

  • Methods: GET, HEAD, POST
  • Headers: Accept, Accept-Language, Content-Language, Content-Type
  • Content-Type: application/x-www-form-urlencoded, multipart/form-data, text/plain

Simple CORS example:

GET /api/products HTTP/1.1
Host: api.shop.com
Origin: https://shop.com

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://shop.com

Preflighted requests (requires OPTIONS preflight):

  • Custom methods: PUT, DELETE, PATCH
  • Custom headers: Authorization, X-API-Key, Content-Type: application/json
  • Any request with credentials (withCredentials: true)

Preflight OPTIONS request:

OPTIONS /api/user/123 HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: authorization, content-type

Preflight response:

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, DELETE, PUT, PATCH
Access-Control-Allow-Headers: authorization, content-type, x-request-id
Access-Control-Max-Age: 86400

Actual DELETE request (after preflight succeeds):

DELETE /api/user/123 HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Authorization: Bearer token123

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://app.example.com

CORS Headers Reference

Access-Control-Allow-Origin:

Access-Control-Allow-Origin: https://app.example.com
  • Specific origin (recommended)
  • * wildcard (public APIs only)
  • Never combine * with credentials (browsers reject)

Dynamic origin whitelisting:

const allowedOrigins = ['https://app1.com', 'https://app2.com'];
const origin = req.headers.origin;
if (allowedOrigins.includes(origin)) {
  res.setHeader('Access-Control-Allow-Origin', origin);
  res.setHeader('Vary', 'Origin'); // Critical for cache correctness
}

Access-Control-Allow-Credentials:

Access-Control-Allow-Credentials: true

Enables cookies, HTTP auth, TLS client certificates in CORS requests.

JavaScript fetch with credentials:

fetch('https://api.example.com/data', {
  credentials: 'include' // Send cookies cross-origin
});

Access-Control-Allow-Methods:

Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS

Comma-separated list of permitted HTTP methods.

Access-Control-Allow-Headers:

Access-Control-Allow-Headers: Content-Type, Authorization, X-API-Key

Custom headers allowed in preflight requests.

Access-Control-Expose-Headers:

Access-Control-Expose-Headers: X-Total-Count, X-Page-Number

Custom response headers JavaScript can read. By default, only simple headers exposed (Cache-Control, Content-Language, Content-Type, Expires, Last-Modified, Pragma).

Access-Control-Max-Age:

Access-Control-Max-Age: 86400

Preflight cache duration (seconds). 86400 = 24 hours. Reduces OPTIONS requests.

CORS Security Vulnerabilities (CWE-942)

Overly Permissive CORS is classified as CWE-942. Common misconfigurations:

Vulnerability 1: Wildcard with credentials

Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

Exploit: Browsers reject this, but misconfigured servers reveal security misunderstanding.

Vulnerability 2: Reflecting arbitrary origins

// DANGEROUS: Reflects any origin without validation
res.setHeader('Access-Control-Allow-Origin', req.headers.origin);
res.setHeader('Access-Control-Allow-Credentials', 'true');

Exploit: Attacker hosts malicious site at https://evil.com, steals user data via credentialed CORS requests.

Secure alternative:

const allowedOrigins = ['https://app.example.com', 'https://mobile.example.com'];
const origin = req.headers.origin;
if (allowedOrigins.includes(origin)) {
  res.setHeader('Access-Control-Allow-Origin', origin);
  res.setHeader('Access-Control-Allow-Credentials', 'true');
  res.setHeader('Vary', 'Origin');
} else {
  // Reject or serve without CORS headers
  res.status(403).send('CORS not allowed');
}

Vulnerability 3: Regex origin validation bypasses

// DANGEROUS: Allows evil.com.attacker.com
if (/example\.com$/.test(origin)) {
  res.setHeader('Access-Control-Allow-Origin', origin);
}

Secure regex:

// Require exact match or trusted subdomains
if (/^https:\/\/([a-z0-9-]+\.)?example\.com$/.test(origin)) {
  res.setHeader('Access-Control-Allow-Origin', origin);
}

CORS security checklist:

  • ✓ Whitelist specific origins (never reflect arbitrary origins)
  • ✓ Use Vary: Origin with dynamic CORS responses
  • ✓ Validate origin against exact match, not substring/regex
  • ✓ Audit allowed methods/headers (principle of least privilege)
  • ✓ Set minimum Access-Control-Max-Age to reduce preflight requests
  • ✓ Never allow credentials with wildcard origin

Testing CORS:

# Test CORS preflight
curl -X OPTIONS https://api.example.com/endpoint \
  -H "Origin: https://app.example.com" \
  -H "Access-Control-Request-Method: DELETE" \
  -H "Access-Control-Request-Headers: authorization" \
  -i

# Test actual CORS request
curl -X GET https://api.example.com/data \
  -H "Origin: https://malicious.com" \
  -i

Use our HTTP Headers Analyzer to audit CORS configuration and identify overly permissive policies.


Part 5: Server Information Disclosure and Hardening

Security Risks of Server Identification Headers

Server information headers leak technology stack details classified as CWE-200 (Information Exposure) and CWE-209 (Generation of Error Message Containing Sensitive Information).

Attack workflow:

  1. Attacker runs reconnaissance: curl -I https://target.com
  2. Headers reveal: Server: Apache/2.4.29 (Ubuntu), X-Powered-By: PHP/7.2.19
  3. Attacker searches NVD database for Apache 2.4.29 CVEs
  4. Finds CVE-2019-0211 (privilege escalation, CVSS 10.0 critical)
  5. Deploys exploit, gains root access

Headers exposing attack surface:

Header Example Value Information Disclosed Risk Level
Server Apache/2.4.29 (Ubuntu) Web server version + OS Critical
X-Powered-By PHP/7.2.19 Language version High
X-AspNet-Version 4.0.30319 .NET Framework version High
X-AspNetMvc-Version 5.2 ASP.NET MVC version Medium
X-Generator WordPress 5.8.1 CMS version Critical
Via 1.1 varnish (Varnish/6.0) Proxy/cache infrastructure Medium
X-Varnish 98765 12345 Varnish request IDs Low
X-Runtime 0.182453 Response time (timing attacks) Low

Header Removal Configuration

Apache (httpd.conf or .htaccess):

# Remove server version
ServerTokens Prod
ServerSignature Off

# Remove X-Powered-By (PHP)
Header unset X-Powered-By

# Alternative: Obfuscate
Header set Server "WebServer"

Nginx (nginx.conf):

# Remove version number
server_tokens off;

# Remove completely via headers module
more_clear_headers 'Server';
more_clear_headers 'X-Powered-By';

# Alternative: Custom server header
more_set_headers 'Server: SecureServer';

PHP (php.ini):

expose_php = Off

Express.js (Node.js):

// Disable X-Powered-By header
app.disable('x-powered-by');

// Use helmet middleware (comprehensive security headers)
const helmet = require('helmet');
app.use(helmet());

ASP.NET (Web.config):

<system.webServer>
  <httpProtocol>
    <customHeaders>
      <remove name="X-Powered-By" />
      <remove name="X-AspNet-Version" />
      <remove name="X-AspNetMvc-Version" />
    </customHeaders>
  </httpProtocol>
  <security>
    <requestFiltering removeServerHeader="true" />
  </security>
</system.webServer>

ASP.NET Core (Startup.cs):

public void Configure(IApplicationBuilder app)
{
    app.Use(async (context, next) =>
    {
        context.Response.Headers.Remove("Server");
        context.Response.Headers.Remove("X-Powered-By");
        await next();
    });
}

Verification:

# Check headers after hardening
curl -I https://example.com

# Should not see version information
# Good: Server: nginx
# Bad:  Server: nginx/1.24.0 (Ubuntu)

Advanced Server Fingerprinting Defense

Beyond header removal:

  1. Response timing normalization — Constant-time responses prevent timing attacks
  2. Error page standardization — Generic error messages, no stack traces
  3. HTTP method filtering — Disable TRACE, TRACK methods
  4. TCP fingerprint randomization — Modify TCP window size, TTL (advanced)

Nginx timing normalization:

# Add random delay to responses (prevents timing analysis)
location / {
    set $delay 0.${msec};
    echo_sleep $delay;
    proxy_pass http://backend;
}

Generic error pages (don’t reveal technology):

error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;

location = /404.html {
    root /usr/share/nginx/html;
    internal;
}

Test server fingerprinting resistance:

# Nmap HTTP fingerprinting
nmap -p 443 --script http-headers,http-server-header example.com

# Shodan search for your servers
curl -s "https://api.shodan.io/shodan/host/YOUR_IP?key=API_KEY" | jq

# Whatweb fingerprinting tool
whatweb https://example.com

Use Shodan and Censys to see what attackers discover about your infrastructure.


Part 6: CDN and Edge Computing Headers

CDN Identification Headers

CDN providers inject custom headers revealing caching behavior, edge locations, and request routing useful for performance debugging.

Cloudflare headers:

CF-RAY: 8b3f2a1c6d4e5f7a-SJC
CF-Cache-Status: HIT
CF-Request-ID: 0b3f2a1c6d4e5f7a
Server: cloudflare

CF-RAY breakdown:

  • 8b3f2a1c6d4e5f7a — Request ID for support tickets
  • SJC — Edge datacenter code (San Jose, California)

CF-Cache-Status values:

  • HIT — Served from cache
  • MISS — Fetched from origin
  • EXPIRED — Cached but expired, revalidated
  • STALE — Served stale during revalidation
  • BYPASS — Cache bypassed (query string, cookie)
  • DYNAMIC — Not cacheable (Set-Cookie present)

AWS CloudFront headers:

X-Amz-Cf-Pop: IAD89-P2
X-Amz-Cf-Id: K2QZ6h5vN8pJYw...
X-Cache: Hit from cloudfront
Via: 1.1 a1b2c3d4e5f6g7.cloudfront.net (CloudFront)
Age: 3425

X-Amz-Cf-Pop:

  • IAD89 — Dulles, Virginia datacenter
  • P2 — Point of presence tier

Map edge location with IP geolocation.

Fastly headers:

X-Served-By: cache-sjc10043-SJC
X-Cache: HIT
X-Cache-Hits: 142
X-Timer: S1700000000.123456,VS0,VE1
Fastly-Debug-Digest: a1b2c3d4e5f6g7h8i9j0

X-Timer breakdown:

  • S1700000000.123456 — Request start timestamp
  • VS0 — Varnish fetch time (0ms = cache hit)
  • VE1 — Total Varnish processing time (1ms)

Varnish Cache headers:

X-Varnish: 98765 12345
Via: 1.1 varnish (Varnish/6.0)
Age: 3425
X-Cache: HIT

X-Varnish values:

  • First number (98765) — Current request ID
  • Second number (12345) — Original cached request ID
  • Single number — Cache miss (no second value)

Age header (RFC 9111 Section 5.1):
Time in seconds since response generated at origin. Indicates cache freshness.

Testing CDN caching:

# Check cache status
curl -I https://cdn.example.com/app.js | grep -i cache

# Purge Cloudflare cache (requires API key)
curl -X POST "https://api.cloudflare.com/client/v4/zones/ZONE_ID/purge_cache" \
  -H "Authorization: Bearer API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"purge_everything":true}'

# Verify Age header increases between requests
curl -I https://example.com/resource
sleep 5
curl -I https://example.com/resource # Age should increase by 5

Performance Monitoring Headers

Server-Timing (W3C Server-Timing API) exposes backend performance metrics visible in browser DevTools:

Server-Timing: db;dur=53, app;dur=47.2, cache;desc="Cache Read";dur=23.2, render;dur=102.5

Server-Timing syntax:

metric-name;dur=duration;desc="description"

Real-world example (Next.js):

Server-Timing:
  total;dur=156.4,
  render;dur=89.3,
  getServerSideProps;dur=45.2,
  db-query;dur=23.1;desc="User profile fetch"

Generating Server-Timing headers:

Express.js middleware:

app.use((req, res, next) => {
  const start = Date.now();

  res.on('finish', () => {
    const duration = Date.now() - start;
    res.setHeader('Server-Timing', `total;dur=${duration}`);
  });

  next();
});

// Detailed timing
app.get('/api/data', async (req, res) => {
  const timings = [];

  const dbStart = Date.now();
  const data = await db.query('SELECT * FROM users');
  timings.push(`db;dur=${Date.now() - dbStart}`);

  const processStart = Date.now();
  const processed = processData(data);
  timings.push(`process;dur=${Date.now() - processStart}`);

  res.setHeader('Server-Timing', timings.join(', '));
  res.json(processed);
});

Reading Server-Timing in browser:

// Performance API
const navigation = performance.getEntriesByType('navigation')[0];
console.log(navigation.serverTiming);

// Output:
// [
//   {name: 'db', duration: 53, description: ''},
//   {name: 'app', duration: 47.2, description: ''}
// ]

Browser DevTools: Chrome/Edge/Firefox Network tab shows Server-Timing metrics visually in waterfall timeline.

Other performance headers:

X-Runtime (Rails):

X-Runtime: 0.182453

Total backend execution time in seconds.

X-Response-Time:

X-Response-Time: 142ms

Millisecond precision response time.

X-Request-ID (distributed tracing):

X-Request-ID: f47ac10b-58cc-4372-a567-0e02b2c3d479

Correlation ID linking frontend errors to backend logs. Use with Datadog, Sentry, Honeycomb.

Timing-Allow-Origin (cross-origin timing):

Timing-Allow-Origin: https://app.example.com

Permits cross-origin access to Resource Timing API. Without this, cross-origin timings are redacted:

// Without Timing-Allow-Origin
const resource = performance.getEntriesByType('resource')[0];
console.log(resource.responseStart); // 0 (redacted)

// With Timing-Allow-Origin
console.log(resource.responseStart); // 1234.56 (actual timing)

Part 7: Content Negotiation and Compression Headers

Content-Type and MIME Types

Content-Type specifies response media type per IANA Media Types Registry, preventing CWE-430 (Deployment of Wrong Handler) MIME confusion attacks.

Content-Type syntax:

Content-Type: type/subtype; parameter=value

Common media types:

Content-Type File Extensions Use Case
text/html; charset=utf-8 .html HTML pages
text/css .css Stylesheets
text/javascript .js, .mjs JavaScript (modern standard)
application/javascript .js JavaScript (legacy)
application/json .json JSON APIs
application/xml .xml XML documents
application/pdf .pdf PDF documents
image/jpeg .jpg, .jpeg JPEG images
image/png .png PNG images
image/webp .webp WebP images
image/svg+xml .svg SVG graphics
video/mp4 .mp4 MP4 videos
application/octet-stream .bin, unknown Binary downloads
multipart/form-data - File upload forms
application/x-www-form-urlencoded - Form submissions

Critical security consideration:
Always combine Content-Type with X-Content-Type-Options: nosniff to prevent browsers from MIME-sniffing uploads:

Content-Type: image/png
X-Content-Type-Options: nosniff

Character encoding:

Content-Type: text/html; charset=utf-8

Essential for international content (prevents Unicode vulnerabilities).

Content-Disposition:

Content-Disposition: attachment; filename="report.pdf"

Triggers browser download instead of inline rendering. Use for:

  • Sensitive documents (force download, not browser viewing)
  • User-generated content (prevent XSS in browser context)
  • Large files (enable resume capability)

Inline viewing:

Content-Disposition: inline; filename="preview.pdf"

Content-Encoding: Compression Optimization

Content-Encoding specifies applied compression algorithm per RFC 9110 Section 8.4.

Compression algorithms:

Encoding Compression Ratio CPU Cost Browser Support Use Case
gzip 60-70% Low 100% Default choice
br (Brotli) 70-85% Medium-High 96%+ (2025) Text assets, modern browsers
deflate 55-65% Low 98% Legacy fallback
compress 50-60% Low Obsolete Avoid (legacy Unix compress)

Content negotiation flow:

Client request:

GET /app.js HTTP/1.1
Accept-Encoding: br, gzip, deflate

Server response (Brotli preferred):

HTTP/1.1 200 OK
Content-Encoding: br
Content-Type: text/javascript
Vary: Accept-Encoding

Nginx Brotli configuration:

# Enable Brotli (requires ngx_brotli module)
brotli on;
brotli_comp_level 6;  # 1-11, higher = better compression, slower
brotli_types text/plain text/css text/xml text/javascript application/javascript application/json application/xml+rss;

# Gzip fallback
gzip on;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml text/javascript application/javascript application/json;

Apache mod_deflate + mod_brotli:

# Brotli
<IfModule mod_brotli.c>
    AddOutputFilterByType BROTLI_COMPRESS text/html text/plain text/css text/javascript application/javascript application/json
    BrotliCompressionQuality 6
</IfModule>

# Gzip fallback
<IfModule mod_deflate.c>
    AddOutputFilterByType DEFLATE text/html text/plain text/css text/javascript application/javascript application/json
</IfModule>

Compression performance:

  • Gzip: 40-80KB/s compression speed, 200-400KB/s decompression
  • Brotli level 6: 10-30KB/s compression, 200-350KB/s decompression
  • Brotli level 11: 1-5KB/s compression (20% better ratio, 5-10x slower)

Best practice:

  • Pre-compress static assets at build time (Brotli level 11)
  • Dynamic compression at runtime (Brotli level 4-6 or gzip level 6)
  • Never compress images/videos (already compressed)
  • Don’t compress small responses (<1KB overhead exceeds savings)

Testing compression:

# Check compression support
curl -I -H "Accept-Encoding: br, gzip" https://example.com/app.js

# Measure compression ratio
curl -H "Accept-Encoding: br" https://example.com/app.js | wc -c  # Compressed
curl https://example.com/app.js | wc -c  # Uncompressed

Content-Language and Internationalization

Content-Language indicates response language(s) per ISO 639 codes:

Content-Language: en-US

Language tag format:

  • Primary language: en, fr, zh
  • Region variant: en-US, en-GB, fr-CA, zh-CN, zh-TW

Multiple languages:

Content-Language: en, fr, de

Content negotiation:

GET /page HTTP/1.1
Accept-Language: fr-CA, fr;q=0.9, en;q=0.8

HTTP/1.1 200 OK
Content-Language: fr-CA

Quality values (q):

  • q=1.0 (default) — Most preferred
  • q=0.9 — Second preference
  • q=0.8 — Third preference

Part 8: HTTP Header Best Practices and Testing

Security Header Deployment Checklist

Minimum security header set (all applications):

Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'; frame-ancestors 'none'
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()

Enhanced security (high-value targets):

Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-random'; upgrade-insecure-requests; block-all-mixed-content; report-uri /csp-reports
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: no-referrer
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=()
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Resource-Policy: same-origin

Remove information disclosure headers:

# Should NOT be present:
# Server: Apache/2.4.29 (Ubuntu)
# X-Powered-By: PHP/7.2.19
# X-AspNet-Version: 4.0.30319

# Acceptable obfuscated alternatives:
Server: WebServer
# or remove entirely

Automated Security Header Testing

Online scanners:

  1. SecurityHeaders.com — Scott Helme’s security header analyzer, A+ grading
  2. Mozilla Observatory — Comprehensive security assessment (headers, TLS, DNSSEC)
  3. Hardenize — Deep infrastructure security audit
  4. Qualys SSL Labs — TLS configuration testing

Command-line tools:

curl inspection:

# Full header dump
curl -I https://example.com

# Specific header
curl -I https://example.com | grep -i strict-transport-security

# Follow redirects
curl -IL https://example.com

# Test CORS
curl -H "Origin: https://evil.com" https://api.example.com/data -i

HTTP Observatory CLI:

# Install
npm install -g observatory-cli

# Scan
observatory https://example.com

# Output:
# Score: 85/100
# Grade: B
# Missing headers: Content-Security-Policy
# Warnings: X-Frame-Options uses SAMEORIGIN (use DENY)

Nmap NSE scripts:

# HTTP header analysis
nmap -p 443 --script http-security-headers example.com

# Server identification
nmap -p 443 --script http-server-header example.com

Browser automation (Playwright):

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  const page = await browser.newPage();

  const response = await page.goto('https://example.com');
  const headers = response.headers();

  console.log('Security Headers:');
  console.log('CSP:', headers['content-security-policy'] || 'MISSING');
  console.log('HSTS:', headers['strict-transport-security'] || 'MISSING');
  console.log('X-Frame-Options:', headers['x-frame-options'] || 'MISSING');

  await browser.close();
})();

CI/CD Integration

GitHub Actions security header testing:

name: Security Header Audit
on: [push, pull_request]

jobs:
  headers:
    runs-on: ubuntu-latest
    steps:
      - name: Test security headers
        run: |
          response=$(curl -I https://staging.example.com)

          # Check required headers
          echo "$response" | grep -q "strict-transport-security" || exit 1
          echo "$response" | grep -q "x-content-type-options" || exit 1
          echo "$response" | grep -q "x-frame-options" || exit 1

          # Ensure info disclosure headers removed
          echo "$response" | grep -q "x-powered-by" && exit 1 || true

          echo "✓ Security headers validated"

GitLab CI security scan:

security-headers:
  stage: test
  script:
    - npm install -g observatory-cli
    - observatory https://staging.example.com --min-score 80

Performance Optimization Strategies

HTTP/2 HPACK header compression:

  • Reduces header overhead 85-95% via static/dynamic table compression
  • Automatic in HTTP/2 (no configuration needed)
  • Benefits: Smaller request/response size, fewer bytes over network

HTTP/3 QPACK:

  • Improved header compression for QUIC transport
  • Handles out-of-order delivery better than HPACK
  • Enable by upgrading to HTTP/3-compatible CDN/server

Header size reduction:

# Audit header sizes
curl -I https://example.com 2>&1 | wc -c

# Remove unnecessary custom headers
# Before: 847 bytes (12 headers)
# After:  412 bytes (8 headers)

Connection: keep-alive optimization:
Implicit in HTTP/2/3, explicit in HTTP/1.1:

Connection: keep-alive
Keep-Alive: timeout=60, max=1000

Reuses TCP connections, reduces TLS handshake overhead.


Part 9: Testing HTTP Headers with Our Analyzer

Using the HTTP Headers Analyzer Tool

Our HTTP Headers Analyzer provides instant security, caching, and CORS header auditing for any website.

Features:

  • Security header validation — Checks OWASP Secure Headers compliance
  • CORS policy analysis — Identifies overly permissive cross-origin configurations
  • Cache directive audit — Validates RFC 9111 caching best practices
  • Server fingerprinting detection — Flags information disclosure vulnerabilities
  • HTTP/2 and HTTP/3 support — Tests modern protocol header compression

Analysis categories:

  1. Security Headers — CSP, HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, COOP, COEP
  2. Caching Headers — Cache-Control, ETag, Last-Modified, Expires, Vary, Pragma, Age
  3. Server Information — Server, X-Powered-By, Via, X-AspNet-Version, X-Generator
  4. Content Headers — Content-Type, Content-Length, Content-Encoding, Content-Language, Content-Disposition
  5. CORS Headers — Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Max-Age, Access-Control-Allow-Credentials

Complementary tools:

Real-world analysis workflow:

  1. Analyze headers with our tool
  2. Identify missing security headers
  3. Configure server to add headers
  4. Re-test to validate changes
  5. Monitor with automated scanning (integrate into CI/CD)
  6. Validate TLS configuration with SSL checker
  7. Test endpoint responses with status checker

Part 10: Common HTTP Header Problems and Solutions

Problem 1: Missing Content-Security-Policy

Symptom:
XSS vulnerabilities, inline script execution, third-party script injection.

Diagnosis:

curl -I https://example.com | grep -i content-security-policy
# (empty result = missing CSP)

Solution:
Start with permissive CSP in report-only mode:

Content-Security-Policy-Report-Only: default-src 'self' https:; script-src 'self' 'unsafe-inline'; report-uri /csp-reports

Analyze violation reports for 2-4 weeks, then enforce strict policy:

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' https://api.example.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'

Validation:
Test with browser DevTools Console (violations logged), CSP Evaluator.

Problem 2: Overly Permissive CORS

Symptom:

Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

Browsers reject this combination, but reveals security misunderstanding.

Diagnosis:

curl -H "Origin: https://evil.com" https://api.example.com/data -i
# Check Access-Control-Allow-Origin response

Solution:
Implement origin whitelist:

const allowedOrigins = ['https://app.example.com', 'https://mobile.example.com'];
const origin = req.headers.origin;

if (allowedOrigins.includes(origin)) {
  res.setHeader('Access-Control-Allow-Origin', origin);
  res.setHeader('Access-Control-Allow-Credentials', 'true');
  res.setHeader('Vary', 'Origin');
} else {
  res.status(403).json({ error: 'CORS not allowed' });
}

Validation:
Test with allowed and disallowed origins, verify Vary: Origin present.

Problem 3: Information Disclosure via Server Headers

Symptom:

Server: Apache/2.4.29 (Ubuntu)
X-Powered-By: PHP/7.2.19
X-AspNet-Version: 4.0.30319

Attackers can search CVE databases for version-specific exploits.

Diagnosis:

curl -I https://example.com | grep -E "Server:|X-Powered-By:|X-AspNet"

Solution:
Remove or obfuscate headers (see Part 5 configuration examples).

Apache:

ServerTokens Prod
ServerSignature Off
Header unset X-Powered-By

Nginx:

server_tokens off;
more_clear_headers 'Server';
more_clear_headers 'X-Powered-By';

Validation:

curl -I https://example.com
# Should see: Server: nginx (no version)
# Or custom: Server: WebServer

Problem 4: Misconfigured Cache-Control Causing Stale Data

Symptom:
Users see outdated content, dashboard shows old data, API returns stale responses.

Diagnosis:

curl -I https://api.example.com/user/profile
# Content-Type: application/json
# Cache-Control: max-age=86400, public  ← WRONG for dynamic data

Solution:
Use appropriate caching for dynamic content:

Cache-Control: private, no-cache

Or sensitive data:

Cache-Control: no-store, no-cache, must-revalidate, private

Validation:
Check Age header (should be 0 for no-cache), verify CDN respects directives.

Problem 5: HSTS Not Working (Mixed Content)

Symptom:

Strict-Transport-Security: max-age=31536000

Set on HTTPS, but some resources load over HTTP.

Diagnosis:
Browser DevTools Console shows “Mixed Content” warnings.

Root cause:
HSTS only upgrades navigation, not subresources with hardcoded http:// URLs.

Solution:

  1. Fix hardcoded HTTP URLs: http://cdn.com/app.jshttps://cdn.com/app.js
  2. Use protocol-relative URLs: //cdn.com/app.js (inherits page protocol)
  3. Add CSP upgrade-insecure-requests directive:
Content-Security-Policy: upgrade-insecure-requests

Automatically upgrades all HTTP subresources to HTTPS.

Validation:
Check browser DevTools Network tab (all resources should use HTTPS).

Problem 6: Cache Hit Rate Low Despite Long max-age

Symptom:

Cache-Control: max-age=86400, public

But CDN shows 30% cache hit rate (expected 80-90%).

Diagnosis:

curl -I https://cdn.example.com/app.js | grep Vary
# Vary: User-Agent, Accept-Encoding, Cookie

Root cause:
High-cardinality Vary headers fragment cache (separate entry per User-Agent).

Solution:
Remove unnecessary Vary headers:

Vary: Accept-Encoding

Only vary on compression, not User-Agent or Cookie.

Alternative for personalized content:

Cache-Control: private, max-age=300

Don’t use shared caching for user-specific responses.

Validation:
Monitor CDN analytics (Cloudflare Analytics, AWS CloudFront Reports) for improved hit rate.


Frequently Asked Questions (FAQ)

What are HTTP response headers?

HTTP response headers are metadata key-value pairs sent by servers in HTTP responses following RFC 9110. They control security policies (CSP, HSTS), caching behavior (Cache-Control, ETag), content negotiation (Content-Type, Content-Encoding), and cross-origin access (CORS headers).

Why are security headers important?

Security headers prevent common web attacks:

  • Content-Security-Policy stops XSS and code injection (CWE-79)
  • Strict-Transport-Security prevents SSL stripping and protocol downgrade
  • X-Frame-Options blocks clickjacking attacks (CWE-1021)
  • X-Content-Type-Options prevents MIME-sniffing XSS
  • Missing security headers enable 43% of web application attacks (Verizon DBIR 2023)

What is the difference between Cache-Control and Expires?

Cache-Control (RFC 9111) is modern, relative-time caching:

Cache-Control: max-age=3600

(Cache for 1 hour from response time)

Expires (legacy HTTP/1.0) uses absolute timestamps:

Expires: Wed, 21 Oct 2025 07:28:00 GMT

Cache-Control supersedes Expires. Use Cache-Control for all modern applications. Expires has clock synchronization issues between client/server.

How do I fix CORS errors?

Diagnosis: Browser console shows “CORS policy: No ‘Access-Control-Allow-Origin’ header present”.

Solution:

  1. Server must send Access-Control-Allow-Origin header:
Access-Control-Allow-Origin: https://app.example.com
  1. For credentialed requests (cookies), add:
Access-Control-Allow-Credentials: true
  1. For custom headers/methods, handle preflight:
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400
  1. Never use * wildcard with credentials (browsers reject).

Test with our HTTP Headers Analyzer.

What headers should I remove for security?

Remove these information disclosure headers:

  • Server: Apache/2.4.29 (Ubuntu) → Remove version
  • X-Powered-By: PHP/7.2.19 → Remove entirely
  • X-AspNet-Version: 4.0.30319 → Remove entirely
  • X-Generator: WordPress 5.8.1 → Remove entirely
  • X-Runtime: 0.182453 → Optional removal (timing attacks)

Keep generic variants:

Server: nginx

(No version information)

See Part 5 for server-specific configuration.

How long should HSTS max-age be?

Minimum: 31536000 seconds (1 year) per RFC 6797.

Recommended: 63072000 seconds (2 years) for HSTS preload eligibility.

Deployment phases:

  1. Test: max-age=300 (5 minutes) to verify no HTTP dependencies
  2. Short-term: max-age=86400 (1 day) with includeSubDomains
  3. Production: max-age=31536000 (1 year) minimum
  4. Preload: max-age=63072000 (2 years) + submit to hstspreload.org

Warning: HSTS is difficult to undo. If you set max-age=31536000, users’ browsers enforce HTTPS for 1 year even if you remove the header.

What is ETag and how does it work?

ETag (Entity Tag) is a cache validation identifier enabling conditional requests:

Strong ETag (content hash):

ETag: "33a64df551425fcc55e"

Workflow:

  1. Server sends ETag with response
  2. Browser stores cached copy + ETag
  3. Next request includes If-None-Match: "33a64df551425fcc55e"
  4. Server compares, returns 304 Not Modified if unchanged
  5. Browser uses cached copy (saves bandwidth)

Benefits:

  • 85-95% bandwidth reduction for unchanged resources
  • Sub-second granularity (better than Last-Modified)
  • Works for generated content without file modification time

How do I implement Content-Security-Policy safely?

Phased rollout (recommended):

Phase 1: Report-Only (2-4 weeks)

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

Monitors violations without blocking. Analyze reports to identify legitimate third-party dependencies.

Phase 2: Permissive Enforcement

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

Enforces basic policy, allows common patterns.

Phase 3: Strict Policy

Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-random123'; style-src 'self'

Remove 'unsafe-inline' using nonces or hashes. Maximum XSS protection.

Tools:

What are the best caching strategies for different resources?

Static immutable assets (content-hashed filenames):

Cache-Control: max-age=31536000, immutable, public

HTML pages:

Cache-Control: no-cache, public

API responses (personalized):

Cache-Control: private, max-age=60

Sensitive data (banking, PII):

Cache-Control: no-store, no-cache, must-revalidate, private

CDN-cached public content:

Cache-Control: public, max-age=300, s-maxage=86400, stale-while-revalidate=86400

See Part 3 for detailed explanations and use cases.

How do I test if my security headers are working?

Online tools:

  1. Our HTTP Headers Analyzer — Instant comprehensive analysis
  2. SecurityHeaders.com — A+ grading system
  3. Mozilla Observatory — Full security audit

Command-line:

# Check all headers
curl -I https://example.com

# Specific header
curl -I https://example.com | grep -i strict-transport-security

# Test CORS
curl -H "Origin: https://evil.com" https://api.example.com/data -i

Browser DevTools:

  • Network tab → Select request → Headers section
  • Console shows CSP violations, mixed content warnings
  • Security tab shows HSTS status (Chrome/Edge)

Automated testing:

# HTTP Observatory CLI
npm install -g observatory-cli
observatory https://example.com

# Nmap scripts
nmap -p 443 --script http-security-headers example.com

Orbit2x Network Security Tools:

External Resources:


Part 1 Complete. This comprehensive guide covers HTTP header fundamentals, security headers (CSP, HSTS, X-Frame-Options), caching optimization (Cache-Control, ETag), CORS configuration, server hardening, CDN headers, and practical troubleshooting.

For additional advanced topics including HTTP/2 header compression (HPACK), HTTP/3 QPACK optimization, WebSocket security headers, and enterprise-scale header policy management, stay tuned for Part 2.

Key Takeaways:

  1. Deploy minimum security header set: CSP, HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy
  2. Remove information disclosure headers: Server versions, X-Powered-By
  3. Implement proper caching: max-age=31536000 for static assets, no-cache for HTML, no-store for sensitive data
  4. Validate CORS carefully: Never use wildcard with credentials, whitelist specific origins
  5. Test regularly: Use our HTTP Headers Analyzer, SecurityHeaders.com, Mozilla Observatory
  6. Monitor continuously: Integrate header testing into CI/CD pipelines
  7. Combine with TLS validation: Check SSL certificates alongside HSTS headers

Use our HTTP Headers Analyzer now to audit your website’s security posture and compliance with OWASP, PCI DSS, and RFC standards.

Related Articles

Continue learning with these related posts

Found This Guide Helpful?

Try our free developer tools that power your workflow. No signup required, instant results.

Share This Article

Help others discover this guide

Share:

Stay Updated

Get notified about new guides and tools