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-Byheaders - 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 origindata:— Data URIs (images as base64)'nonce-r4nd0m'— Cryptographic nonce for specific inline scripts'sha256-hash'— SHA-256 hash of allowed inline contenthttps://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:
- Using
'unsafe-inline'defeats XSS protection (use nonces instead) - Overly broad wildcards like
https://*(specify exact origins) - Missing
frame-ancestors(still vulnerable to clickjacking) - Not monitoring violation reports (blind to policy effectiveness)
- 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:
- User types
example.com→ Browser sends HTTP request - Attacker intercepts, proxies HTTPS to server, serves HTTP to user
- User sees HTTP, session hijacked, credentials stolen
- Server redirects
HTTP → HTTPSbut attacker already has session
With HSTS:
- User types
example.com→ Browser internally upgrades to HTTPS - Bypass attacker’s MitM proxy, establish direct TLS connection
- Invalid certificate rejected, connection terminated
- 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:
- Valid HTTPS certificate on all subdomains
- Redirect HTTP → HTTPS on base domain
- Serve HSTS header on base domain with:
max-age≥ 31536000 (1 year)includeSubDomainsdirectivepreloaddirective
- 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:
- Validate HSTS alongside TLS configuration with SSL Certificate Checker
- Test HTTPS accessibility via HTTP Status Checker
- Verify edge server HSTS headers from different geographic locations using IP Lookup
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:
- Attacker embeds your site in invisible iframe over malicious page
- User thinks they’re clicking attacker’s button, actually clicking your “Transfer Funds” button
- Transparent iframe positioned precisely to capture clicks
- 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:
- Attacker uploads “image” containing HTML/JavaScript disguised as image
- Server sets
Content-Type: image/pngbased on file extension - Browser sniffs content, detects HTML, renders as HTML page
- 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 accesssame-origin-allow-popups— Isolate except popups opened by documentsame-origin— Strict isolation, separates browsing context
Cross-Origin-Embedder-Policy (COEP):
Cross-Origin-Embedder-Policy: require-corp
Values:
unsafe-none— Default, no restrictionsrequire-corp— All subresources must have CORS orCross-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-Matchwith ETag) - Returns
304 Not Modifiedif 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-storefor 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-maxageoverridesmax-agefor shared cachesno-storeoverrides everything (never cache)privateoverridespublicimmutableprevents revalidation even ifmax-ageexpired
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-cachefor 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-second precision — Files modified multiple times per second appear unchanged
- Clock synchronization — Server time drift causes stale content
- File restoration — Restoring from backup changes mtime but not content
- 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:443≠http://example.com:80(different protocol)https://api.example.com≠https://example.com(different subdomain)- JavaScript from
https://app.comcannot read responses fromhttps://api.other.comby 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: Originwith dynamic CORS responses - ✓ Validate origin against exact match, not substring/regex
- ✓ Audit allowed methods/headers (principle of least privilege)
- ✓ Set minimum
Access-Control-Max-Ageto 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:
- Attacker runs reconnaissance:
curl -I https://target.com - Headers reveal:
Server: Apache/2.4.29 (Ubuntu),X-Powered-By: PHP/7.2.19 - Attacker searches NVD database for Apache 2.4.29 CVEs
- Finds CVE-2019-0211 (privilege escalation, CVSS 10.0 critical)
- 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:
- Response timing normalization — Constant-time responses prevent timing attacks
- Error page standardization — Generic error messages, no stack traces
- HTTP method filtering — Disable TRACE, TRACK methods
- 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 ticketsSJC— Edge datacenter code (San Jose, California)
CF-Cache-Status values:
HIT— Served from cacheMISS— Fetched from originEXPIRED— Cached but expired, revalidatedSTALE— Served stale during revalidationBYPASS— 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 datacenterP2— 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 timestampVS0— 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 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 preferredq=0.9— Second preferenceq=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:
- SecurityHeaders.com — Scott Helme’s security header analyzer, A+ grading
- Mozilla Observatory — Comprehensive security assessment (headers, TLS, DNSSEC)
- Hardenize — Deep infrastructure security audit
- 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:
- Security Headers — CSP, HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, COOP, COEP
- Caching Headers — Cache-Control, ETag, Last-Modified, Expires, Vary, Pragma, Age
- Server Information — Server, X-Powered-By, Via, X-AspNet-Version, X-Generator
- Content Headers — Content-Type, Content-Length, Content-Encoding, Content-Language, Content-Disposition
- CORS Headers — Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Max-Age, Access-Control-Allow-Credentials
Complementary tools:
- SSL Certificate Checker — Validate TLS configuration alongside HSTS headers
- HTTP Status Checker — Test endpoint accessibility and redirect chains
- My IP Address — Identify CDN edge location for geographic header testing
- DNS Lookup — Verify DNS configuration for multi-CDN setups
Real-world analysis workflow:
- Analyze headers with our tool
- Identify missing security headers
- Configure server to add headers
- Re-test to validate changes
- Monitor with automated scanning (integrate into CI/CD)
- Validate TLS configuration with SSL checker
- 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:
- Fix hardcoded HTTP URLs:
http://cdn.com/app.js→https://cdn.com/app.js - Use protocol-relative URLs:
//cdn.com/app.js(inherits page protocol) - Add CSP
upgrade-insecure-requestsdirective:
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:
- Server must send
Access-Control-Allow-Originheader:
Access-Control-Allow-Origin: https://app.example.com
- For credentialed requests (cookies), add:
Access-Control-Allow-Credentials: true
- 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
- 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 versionX-Powered-By: PHP/7.2.19→ Remove entirelyX-AspNet-Version: 4.0.30319→ Remove entirelyX-Generator: WordPress 5.8.1→ Remove entirelyX-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:
- Test:
max-age=300(5 minutes) to verify no HTTP dependencies - Short-term:
max-age=86400(1 day) withincludeSubDomains - Production:
max-age=31536000(1 year) minimum - 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:
- Server sends ETag with response
- Browser stores cached copy + ETag
- Next request includes
If-None-Match: "33a64df551425fcc55e" - Server compares, returns
304 Not Modifiedif unchanged - 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:
- CSP Evaluator — Google security team validator
- Report URI CSP Builder — Visual policy generator
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:
- Our HTTP Headers Analyzer — Instant comprehensive analysis
- SecurityHeaders.com — A+ grading system
- 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
Related Tools and Resources
Orbit2x Network Security Tools:
- HTTP Headers Analyzer — Analyze security, caching, CORS headers
- SSL/TLS Certificate Checker — Validate HTTPS configuration and certificate chain
- HTTP Status Code Checker — Test endpoint responses and redirects
- My IP Address — Identify public IP and CDN edge location
- DNS Lookup — Verify DNS configuration and propagation
External Resources:
- RFC 9110 — HTTP Semantics (official specification)
- RFC 9111 — HTTP Caching standard
- OWASP Secure Headers Project — Security header guidelines
- W3C CSP Level 3 — Content-Security-Policy specification
- MDN HTTP Headers — Comprehensive header reference
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:
- Deploy minimum security header set: CSP, HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy
- Remove information disclosure headers: Server versions, X-Powered-By
- Implement proper caching:
max-age=31536000for static assets,no-cachefor HTML,no-storefor sensitive data - Validate CORS carefully: Never use wildcard with credentials, whitelist specific origins
- Test regularly: Use our HTTP Headers Analyzer, SecurityHeaders.com, Mozilla Observatory
- Monitor continuously: Integrate header testing into CI/CD pipelines
- 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.