You spent an afternoon adding Strict-Transport-Security, Content-Security-Policy, and X-Frame-Options to your nginx config. Your security scanner gave you an A. Three months later you deploy a new CDN configuration and your grade drops to a D. Nobody noticed.
This is security header regression — and it is far more common than most developers realize.
The most common causes of security header regression, in rough order of frequency:
Security headers are often set at the edge (Cloudflare, Fastly, AWS CloudFront) rather than in the application itself. When you update a CDN config, create a new distribution, or change cache rules, header directives can silently disappear. The deployment succeeds with zero errors — because from the server's perspective, nothing failed.
Your security headers might live in a location / block. When you deploy a new server block or a new vhost, the add_header directives from the parent block may not propagate into nested location blocks — this is a known nginx behavior. Adding one innocent location block can silently drop all headers from that path.
# This works for /, but adding a nested block can break header inheritance:
server {
add_header X-Frame-Options "SAMEORIGIN";
add_header Content-Security-Policy "default-src 'self'";
location / {
try_files $uri $uri/ =404;
}
# Adding this block causes the above headers to stop applying to /api/:
location /api/ {
proxy_pass http://localhost:3000;
# The add_header directives above are now NOT sent for /api/ requests
}
}
The fix is to repeat add_header in every nested location block, or to use always in a way that applies globally. But the failure mode is silent — your / route still returns headers, just not /api/ ones.
AWS ALB, GCP Cloud Load Balancing, and Azure Application Gateway can all be configured to add or strip response headers. When infra teams change load balancer rules, security headers can vanish from production without any application change. These updates don't trigger app deployment pipelines, so they don't show up in deploy logs.
Migrating from Heroku to Render, from Nginx to Caddy, or from a custom server to Vercel often resets security headers to defaults. Each platform has its own mechanism for setting response headers, and headers from the previous platform are invisible to the new one.
If your headers are set by a middleware library (helmet.js, Django SecurityMiddleware, Spring Security), a major version upgrade can change defaults. Helmet v5 shipped with different defaults than Helmet v4. Upgrading silently changed what helmet() with no arguments returned.
Security header regressions are invisible by default. Unlike a broken API endpoint that returns 500 errors, a missing security header:
The consequence of a missing Content-Security-Policy is that an XSS attack that would have been blocked can now succeed. The consequence of a missing Strict-Transport-Security header is that users on insecure networks are vulnerable to SSL stripping. These consequences are probabilistic and delayed — so the regression goes unnoticed until a security audit or a bug bounty report.
The lowest-effort option that requires the most discipline. After every deployment, run:
curl -s -I https://yourdomain.com | grep -i -E "strict-transport|content-security|x-frame|x-content-type|referrer-policy|permissions-policy"
Or use a free checker like the security headers checker to see your current grade at a glance.
The problem: this only works if you remember to check. Regressions caused by infrastructure changes (CDN reconfigs, load balancer updates) often happen outside of app deployment cycles, so "check after each deploy" misses them entirely.
A better approach is to assert specific header values in integration tests or as a post-deploy smoke test:
# In your CI pipeline (curl-based smoke test)
HEADERS=$(curl -s -I https://staging.yourdomain.com)
assert_header() {
if ! echo "$HEADERS" | grep -qi "$1"; then
echo "MISSING SECURITY HEADER: $1"
exit 1
fi
}
assert_header "strict-transport-security"
assert_header "content-security-policy"
assert_header "x-frame-options"
assert_header "x-content-type-options"
echo "All security headers present ✓"
This catches regressions introduced by application deployments. It still misses infrastructure-level changes.
The most reliable approach is to check your headers from outside your infrastructure on a regular schedule — and send an alert if anything changes or disappears.
This catches regressions from all causes: application deployments, CDN changes, load balancer updates, certificate renewals that reset server configs, and anything else that touches your response headers.
HeaderGuard monitors your HTTP security headers daily and sends you an email the moment a header disappears or its value changes — before a security scanner or bug bounty hunter finds it for you.
No agents to install, no CI pipeline changes. Just add your domain and HeaderGuard handles the rest.
Join the HeaderGuard waitlist → Check your headers now (free)Focus on the six headers that carry the most security weight:
| Header | What you lose when it disappears |
|---|---|
Strict-Transport-Security | HTTPS enforcement — users become vulnerable to SSL stripping on insecure networks |
Content-Security-Policy | XSS mitigation — script injection attacks that were blocked can now succeed |
X-Frame-Options | Clickjacking protection — your pages can be embedded in malicious iframes |
X-Content-Type-Options | MIME sniffing prevention — browsers may execute content as a different type than intended |
Referrer-Policy | Referrer leakage — full URLs (potentially with tokens or PII) may leak to third-party sites |
Permissions-Policy | Feature access — camera, microphone, and geolocation permissions revert to browser defaults |
The most impactful regressions to catch immediately are Strict-Transport-Security (direct MITM risk) and Content-Security-Policy (XSS mitigation). The others reduce your security posture but are less immediately exploitable.
HeaderGuard checks your domain's security headers daily from external infrastructure — outside your own servers, CDN, and load balancers. When any monitored header changes or disappears, it sends you an email immediately.
The key difference from CI pipeline checks: HeaderGuard catches infrastructure-level regressions that never touch your application code or deployment pipeline. CDN configuration updates, load balancer rule changes, and certificate renewals that reset server configs are all visible to an external monitor — and invisible to a deployment-triggered smoke test.
Check your current security headers → to see your baseline, then join the HeaderGuard waitlist to get alerted when anything changes.