When we talk about hardening a web application, attention usually goes to patching, WAFs, or TLS configurations. But one of the simplest (and often overlooked) ways to shrink your attack surface is through HTTP header hygiene.
This post takes a technical look at why headers matter, which ones to use, how to test them, and the common pitfalls attackers love to exploit.
What Is HTTP Header Hygiene?
In every HTTP request/response, headers define how the browser and server should behave. Some are functional (like Content-Type), others are purely security-related (like Content-Security-Policy).
Poor hygiene can mean:
- Missing headers → no defences in place.
- Misconfigured headers → false sense of security, breachable protections.
- Leaky headers → unnecessary exposure of software versions, frameworks, or internal info.
Why Bad Headers Are Dangerous
Attackers often enumerate headers early in reconnaissance. Weak headers can:
- Enable XSS by not restricting script sources.
- Enable clickjacking by not blocking iframes.
- Leak software versions (Server: Apache/2.4.29) → mapped to CVEs.
- Allow downgrade attacks if HSTS is missing.
Simply put, bad header hygiene increases the attack surface with zero effort from the attacker.
A Trend We’re Seeing: Misconfigured Headers Everywhere
Across industries, we consistently see headers either missing, outdated, or incorrectly configured at scale. This isn’t just anecdotal, it’s supported by large-scale internet scans:
Current Adoption (ThingsRecon Data):
- Content-Security-Policy (CSP): only ~18% of sites deploy CSP at all. Worse, 90% of those still allow 'unsafe-inline', and 74% allow 'unsafe-eval', weakening protection dramatically.
- HSTS (Strict-Transport-Security): seen on only ~28% of responses on mobile sites, far from universal.
- X-Frame-Options: adoption sits below 40% across sites — meaning most web apps are still clickjacking-exposed.
- Leaky headers: exposure of Server and X-Powered-By values remains one of the most common issues.
Some recurring missteps include:
- Absent CSP → leaving apps exposed to XSS.
- Weak HSTS → max-age values too short to matter.
- Overly permissive CORS → Access-Control-Allow-Origin: * exposing APIs.
- Clickjacking exposure → no X-Frame-Options or frame-ancestors.
The trend is clear: while teams prioritize patching and TLS, basic header hygiene is consistently neglected, especially in cloud-native and CI/CD-driven environments where defaults often leave gaps.
Essential Security Headers
Strict-Transport-Security (HSTS)
Forces HTTPS and prevents downgrade/SSL-strip attacks.
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
- max-age: browsers enforce HTTPS for the specified seconds.
- includeSubDomains: applies policy everywhere.
- preload: adds site to browser preload lists.
Bad: Strict-Transport-Security: max-age=300 (ineffective).
Good: at least 31536000 (1 year).
Content-Security-Policy (CSP)
Restricts what resources the browser can load.
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; object-src 'none'
Bad: Content-Security-Policy: * (no protection).
Good: granular, no wildcards, no unsafe-inline.
X-Frame-Options / frame-ancestors
Prevents clickjacking.
X-Frame-Options: DENY
# or modern equivalent:
Content-Security-Policy: frame-ancestors 'none'
Use CSP frame-ancestors for modern protection; keep X-Frame-Options if you already set it.
Referrer-Policy
Controls what referrer info is leaked.
Referrer-Policy: strict-origin-when-cross-origin
This is now the default in major browsers and recommended for balance.
Permissions-Policy
Restricts access to features like camera, mic, and geolocation.
Permissions-Policy: geolocation=(), microphone=(), camera=()
Adoption is still limited, but it reduces unnecessary risk.
Cache-Control
Controls storage of sensitive data.
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
- no-store: critical for sensitive data.
- no-cache: requires revalidation.
- Pragma: legacy support.
Server & X-Powered-By
Avoid leaking framework/version details.
Bad: Server: Apache/2.4.29 (Ubuntu)
Good: Server: nginx or remove entirely.
Common Header Misconfigurations
- CSP with wildcards (*) → allows arbitrary injection.
- Short HSTS max-age → useless protection.
- Duplicate headers (conflicts).
- Overly permissive CORS (Access-Control-Allow-Origin: *) → exposes APIs.
How to Test Header Hygiene
- curl: curl -I https://example.com
- Nmap: nmap --script http-headers -p80,443 example.com
- Python requests:
- import requests
- r = requests.get("https://example.com")
- for k, v in r.headers.items():
- print(f"{k}: {v}")
- Scanners: of course
Best Practices
- Baseline & Audit: Conduct regular automated scans.
- Centralize Config: Set headers in nginx/Apache/reverse proxy.
- Shift Left: Enforce in CI/CD pipelines.
- Monitor Drift: Continuously track with external attack surface monitoring.
HTTP headers may seem like a “small” detail, but attackers thrive on small gaps.
And the evidence is overwhelming: CSP is rare, HSTS is missing on most sites, XFO adoption lags, and misconfigurations are the norm. Attackers don’t need zero-days when missteps this basic are everywhere.
Fixing header hygiene takes minutes, and blocks entire attack paths.