HTTP Response Headers Analysis: A Complete Guide to Security Scoring and Performance Optimization
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 defaultscript-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 oldX-Frame-Optionsconnect-src: Limitsfetch/XMLHttpRequesttarget 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 yearincludeSubDomains: Applies to all subdomainspreload: 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 serversprivate: Only browser can cachemax-age: Cache validity period (seconds)immutable: Resource never changes; skip revalidation even on refreshmust-revalidate: Must revalidate after expirationno-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:
Access-Control-Allow-Origincannot be*whenAllow-Credentials: true- Preflight requests (OPTIONS) need separate handling, return 204
Access-Control-Allow-Headersmust 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