HTTP Response Headers Analysis: A Complete Guide to Security Scoring and Performance Optimization#

Recently, while debugging a production issue, I noticed our API responses were painfully slow. Opening DevTools revealed the culprit: Cache-Control headers were completely missing. Every request fetched resources fresh from the server. This got me thinking—HTTP response headers are the “invisible configuration” that’s often overlooked, yet they have a massive impact on both security and performance.

So I built a tool to automatically analyze response headers, and along the way, documented everything I learned.

HTTP Response Header Categories#

Response headers fall into four main categories:

Category Key Headers Purpose
General Content-Type, Content-Length, Date, Server Basic information
Security Content-Security-Policy, Strict-Transport-Security, X-Frame-Options Protection against XSS, clickjacking, etc.
Caching Cache-Control, ETag, Last-Modified, Expires Cache strategy control
CORS Access-Control-Allow-Origin, Access-Control-Allow-Methods Cross-origin resource sharing

Security Headers: The First Line of Defense#

Content-Security-Policy (CSP)#

CSP is the most powerful security header—it stops XSS attacks at the source:

Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 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'

Key directives:

  • default-src 'self': Only allow same-origin resources by default
  • script-src: Controls script sources; 'unsafe-inline' allows inline scripts (not recommended, but many legacy projects need it)
  • frame-ancestors 'none': Prevents iframe embedding, replacing the old X-Frame-Options
  • connect-src: Limits fetch/XMLHttpRequest target domains

Pro tip: Misconfigured CSP can break your entire site. Test with Content-Security-Policy-Report-Only first—it reports violations without blocking:

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

Strict-Transport-Security (HSTS)#

Forces HTTPS usage, preventing man-in-the-middle attacks:

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
  • max-age=31536000: Valid for one year
  • includeSubDomains: Applies to all subdomains
  • preload: Submit to browser’s built-in HSTS preload list

Warning: Ensure all resources support HTTPS before enabling HSTS. Once browsers cache HSTS, they’ll force HTTPS even if the server disables the header—until max-age expires.

X-Frame-Options#

Prevents clickjacking attacks:

X-Frame-Options: DENY
# or
X-Frame-Options: SAMEORIGIN

DENY blocks all embedding; SAMEORIGIN allows same-origin embedding. Modern browsers prefer CSP’s frame-ancestors, but configure both for compatibility.

X-Content-Type-Options#

Prevents MIME type sniffing:

X-Content-Type-Options: nosniff

Without this header, browsers might execute text/plain files as HTML, leading to XSS vulnerabilities.

Referrer-Policy#

Controls Referer header behavior:

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

Common values compared:

Value Same-origin Cross-origin
no-referrer Not sent Not sent
origin Send origin Send origin
strict-origin Send origin HTTPS→HTTPS sends origin, otherwise nothing
strict-origin-when-cross-origin Send full URL Same as strict-origin

Recommend strict-origin-when-cross-origin for a balance of privacy and functionality.

Caching Headers: The Core of Performance Optimization#

Cache-Control#

The most powerful caching header:

# Static assets (long-term cache)
Cache-Control: public, max-age=31536000, immutable

# API responses (short-term cache)
Cache-Control: private, max-age=300, must-revalidate

# No caching
Cache-Control: no-store, no-cache, must-revalidate

Key directives:

  • public: Can be cached by proxy servers
  • private: Only browser can cache
  • max-age: Cache validity period (seconds)
  • immutable: Resource never changes; skip revalidation even on refresh
  • must-revalidate: Must revalidate after expiration
  • no-cache: Must revalidate before use (not “don’t cache”)
  • no-store: Don’t cache at all

ETag and Last-Modified#

Work with Cache-Control for conditional requests:

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

Browser sends on next request:

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

If unchanged, server returns 304 Not Modified, saving bandwidth.

ETag generation strategies:

// Simple approach: file hash
const etag = crypto.createHash('md5').update(content).digest('hex')

// Nginx default: modification time + file size
// ETag: "5a3b2c1d-1234"

CORS Headers: Cross-Origin Resource Sharing#

In frontend-backend separation architectures, CORS misconfiguration is one of the most common issues:

Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 86400

Key points:

  1. Access-Control-Allow-Origin cannot be * when Allow-Credentials: true
  2. Preflight requests (OPTIONS) need separate handling, return 204
  3. Access-Control-Allow-Headers must include all custom headers

Node.js middleware implementation:

app.use((req, res, next) => {
  const allowedOrigins = ['https://example.com', 'https://admin.example.com']
  const origin = req.headers.origin
  
  if (allowedOrigins.includes(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin)
  }
  
  res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS')
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type,Authorization')
  res.setHeader('Access-Control-Allow-Credentials', 'true')
  res.setHeader('Access-Control-Max-Age', '86400')
  
  if (req.method === 'OPTIONS') {
    return res.status(204).end()
  }
  next()
})

Security Scoring Algorithm#

Implementing a simple security scoring system:

const securityScoreWeights: Record<string, number> = {
  'content-security-policy': 20,
  'strict-transport-security': 15,
  'x-frame-options': 15,
  'x-content-type-options': 15,
  'referrer-policy': 15,
  'permissions-policy': 10,
  'x-xss-protection': 10,  // Legacy browser compatibility
}

function calculateSecurityScore(headers: Record<string, string>): number {
  let score = 0
  for (const [key, weight] of Object.entries(securityScoreWeights)) {
    if (headers[key]) {
      score += weight
    }
  }
  return score
}

Scoring standards:

  • 0-39: Dangerous, missing critical security headers
  • 40-69: Fair, recommend adding missing headers
  • 70-100: Good, security configuration is solid

Common Issues and Solutions#

1. CSP Blocks Inline Scripts#

Many legacy projects depend on inline scripts. After configuring CSP:

Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self'"

Solution: Use nonce or hash:

Content-Security-Policy: script-src 'self' 'nonce-abc123'
<script nonce="abc123">
  // inline code
</script>

2. HSTS Prevents HTTP Access#

Development environments might not have HTTPS, but browser already cached HSTS.

Solutions:

  • Chrome: Visit chrome://net-internals/#hsts, delete the domain
  • Firefox: Clear “HSTS state” from browser history

3. CORS Preflight Fails#

Symptom: POST requests return 401/403, but GET works fine.

Cause: Preflight (OPTIONS) doesn’t carry credentials, but server requires authentication.

Solution:

// Preflight doesn't need authentication
if (req.method === 'OPTIONS') {
  return res.status(204).end()
}

Practical Tool#

Online analyzer: HTTP Header Analyzer

Enter a URL to get:

  • Security score with missing header alerts
  • Categorized header information
  • Raw headers for copying

Related tools: DNS Lookup Tool | SSL Certificate Checker