Security headers came up exactly twice in the last decade of agency work:
- When a client's enterprise customer ran a Mozilla Observatory scan, scored your client F, and asked them to "address the security headers issue before we can proceed."
- When a payment processor's onboarding asked for "evidence of HSTS, CSP, and mixed content remediation."
In both cases, the agency operator hadn't thought about security headers since their last bootcamp module, scrambled to add them to the nginx config, and rated it down as a "annoying compliance ticket." That's fine. But security headers are also the cheapest 30-minute upgrade you can offer a care plan client — they don't break anything when set correctly, they improve the client's security posture meaningfully, and they pass automated audits that increasingly gate B2B sales.
This is the playbook.
The headers that actually matter in 2026
There are a dozen security headers in the wild. Five of them carry 90% of the weight:
| Header | What it does | How often it's missing |
|---|---|---|
| Strict-Transport-Security (HSTS) | "Always connect via HTTPS, never HTTP" | ~40% of agency sites |
| Content-Security-Policy (CSP) | "Only run scripts/load resources from these sources" | ~70% missing or broken |
| X-Frame-Options (or CSP frame-ancestors) | "Don't let other sites iframe me" | ~30% missing |
| Referrer-Policy | "What to include in Referer when leaving my site" | ~50% missing |
| Permissions-Policy | "Disable camera/mic/geolocation by default" | ~80% missing |
Two more come up occasionally:
X-Content-Type-Options: nosniff— prevents MIME type guessing. Universal, zero downside.Cross-Origin-Opener-Policy/Cross-Origin-Embedder-Policy— for sites that need cross-origin isolation. Rare in agency work.
I'll skip the rest. If you're setting up a site for a client who needs SOC 2 or HIPAA, ask their compliance team for the full list.
HSTS — set this first
Header: Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Translation: "For the next year, browsers must access me via HTTPS only. Apply this to my subdomains too. I'm requesting inclusion in the HSTS preload list shipped with browsers."
Why this matters: without HSTS, a user who types client.com in their address bar will first hit http://client.com, then get a 301 redirect to https://. That HTTP hop is where a network attacker can intercept and downgrade. HSTS tells the browser to never try HTTP for this domain after the first visit.
The three flags
max-age=31536000— one year. Don't go shorter than 6 months.includeSubDomains— apply to*.client.com. Only set this if you're certain every subdomain (including ones you haven't deployed yet) supports HTTPS. Otherwise you'll break astaging.client.comthat's still on HTTP.preload— request browser preload-list inclusion. Once added, removing the domain from preload takes months. Set this only after the site has been on HSTS-with-includeSubDomains for at least a month with no issues.
The "I forgot HTTPS works on a subdomain" trap
We've seen this twice: agency sets includeSubDomains because the main site works fine. Six months later, a developer launches dev.client.com on HTTP for internal testing. Browsers refuse to load it. Developer is confused. Takes hours to root-cause.
Recommendation: deploy HSTS in stages.
- Week 1:
max-age=300(5 minutes) — easy to roll back - Week 2:
max-age=3600(1 hour) — still rollback-able - Week 3-4:
max-age=2592000(30 days) - Month 2:
max-age=31536000(1 year) +includeSubDomainsif all subdomains confirmed - Month 3+:
preloadflag
This is conservative. For a brand-new site you can jump straight to a year — the rollback cost only matters for sites with existing user traffic.
CSP — the hard one
Header (minimal): Content-Security-Policy: default-src 'self'; img-src 'self' data: https:; script-src 'self'; style-src 'self' 'unsafe-inline'
Translation: "Only load resources from my own origin, except images can be data URLs or any HTTPS, and inline styles are okay."
CSP is the most powerful security header and the most likely to break your site. It exists because cross-site scripting (XSS) is the #1 web vulnerability and CSP gives you a hard whitelist of what scripts can run.
Why agencies skip CSP
CSP breaks third-party integrations. The most common agency-side third parties:
- Google Analytics → needs
*.google-analytics.com,*.googletagmanager.com - Facebook Pixel → needs
*.facebook.net,connect.facebook.net - HubSpot → needs
*.hubspot.com,*.hsforms.com, several others - Calendly → needs
*.calendly.com - Intercom → needs
*.intercom.io,*.intercom.com,*.intercomcdn.com - Stripe Checkout → needs
js.stripe.com,m.stripe.network - Cloudflare Insights → needs
static.cloudflareinsights.com
Setting CSP "properly" on a site with all of these requires building a long allowlist, which then needs maintenance every time the client adds a new tool. That maintenance is unglamorous, and so most agency-managed sites have no CSP at all.
The reporting-only path
Easier to ship CSP via the reporting-only mode first:
Content-Security-Policy-Report-Only: default-src 'self'; ...; report-uri /csp-report
This sends violation reports without actually blocking. Run it for two weeks, collect what would have been blocked, build the allowlist, then promote to enforced Content-Security-Policy. Tools like Report URI or self-hosted endpoints catch the reports.
For agency clients without a robust dev team, "set CSP-Report-Only + collect reports + tighten over time" is the realistic path. Going straight to enforced CSP without instrumenting first will break things you didn't expect.
The pragmatic minimum
If you don't want to invest in full CSP, the minimum that still scores in Mozilla Observatory and similar audits:
Content-Security-Policy: default-src 'self' https:; script-src 'self' 'unsafe-inline' https:; style-src 'self' 'unsafe-inline' https:; img-src 'self' data: https:; font-src 'self' data: https:; frame-ancestors 'none';
This is permissive (any HTTPS source is allowed) but it gets you frame-ancestors 'none' which is the main XSS-defense win, and it satisfies most automated scanners. Not ideal but realistic for the median agency client.
X-Frame-Options / frame-ancestors
Header: X-Frame-Options: DENY (or SAMEORIGIN)
OR in CSP: frame-ancestors 'none'
Translation: "Don't let any site embed me in an iframe."
This prevents clickjacking — attacker frames your client's site inside their own page, overlays invisible buttons, and tricks users into clicking through. Universal default: deny framing entirely, unless your client genuinely needs to be embedded (rare).
X-Frame-Options is the older mechanism, still respected by all browsers. frame-ancestors in CSP is the modern way and supersedes X-Frame-Options. Setting both is fine.
Referrer-Policy
Header: Referrer-Policy: strict-origin-when-cross-origin
Translation: "When users leave my site, only send the origin (not the full URL path) as Referer. Within my site, full URL is fine."
Why this matters: by default, when a user clicks a link from your client's site to a third party, the browser sends the full URL of the source page in the Referer header. If your client has URLs like /clients/acme-corp/internal-report?token=xyz123, that token leaks to whatever external site they linked to.
strict-origin-when-cross-origin is the sensible default — origin-only externally, full URL internally.
Permissions-Policy
Header: Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()
Translation: "Don't allow JavaScript on this page to access camera, mic, geolocation, or payment APIs at all."
For a typical agency client (marketing site, e-commerce, lead-gen) — none of these APIs should be reachable from JS. Setting this header to disable them by default prevents drive-by exploitation if a third-party script gets compromised.
Adjust the list if your client actually needs one of these (e.g. enabling payment for a Stripe Checkout integration). For most clients: disable all.
What "passes a scan" actually means
The three audit tools clients' enterprise customers actually use:
- Mozilla Observatory (observatory.mozilla.org/analyze/client.com) — grades A+ to F based on header presence and configuration. Includes a downloadable PDF.
- securityheaders.com — Scott Helme's site. Same idea, slightly different scoring.
- SSL Labs — primarily for TLS but also checks HSTS preload-list status.
Getting an A on Observatory typically requires:
- HSTS with
max-age >= 31536000andincludeSubDomains - CSP that doesn't use
unsafe-inlinefor scripts (the hardest one) - X-Frame-Options DENY or frame-ancestors 'none'
- X-Content-Type-Options: nosniff
- Referrer-Policy set
- All cookies marked Secure + HttpOnly + SameSite
A+ requires HSTS preload (preload flag + listed in preload list).
For agency clients without unusually high security needs, A grade is a reasonable target. A+ requires the preload commitment which not everyone wants.
Monitoring security headers over time
Headers drift. Reasons:
- Client's dev pushes a new nginx config and the security headers block doesn't get carried over
- The client migrates from one host to another, headers were configured at the load balancer level, didn't migrate
- A WordPress plugin or Webflow update changes the headers
- Cloudflare WAF rule that injected headers gets disabled by a Cloudflare admin
The visible impact: the Mozilla Observatory score drops from A to C overnight. The agency doesn't notice until the next time a client runs a scan.
External daily probe: hit the homepage with curl -I https://client.com, parse the response headers, compute the Observatory-style score, alert when the score drops a grade.
This is what Pleenx's security-headers probe does. Daily check, grade A-F, alert on drift. Same probe family as SSL or DNS.
What to charge for "security headers compliance"
The 30-minute initial setup is part of the care plan. The daily monitoring + monthly re-check + remediation when drift is detected is worth $20-50 per client per month, embedded in a larger retainer.
Don't sell it as "security headers." Sell it as "your site stays A-grade in Mozilla Observatory and we'll fix it if it drops." That's the language clients' enterprise customers will use when they ask.
TL;DR
- Five security headers carry 90% of the weight: HSTS, CSP, X-Frame-Options, Referrer-Policy, Permissions-Policy.
- HSTS: easy, set conservatively with staged max-age.
- CSP: hard, ship in report-only mode first, tighten over time.
- X-Frame-Options DENY: universal default.
- Referrer-Policy:
strict-origin-when-cross-originis the sensible default. - Permissions-Policy: disable camera/mic/geolocation/payment unless explicitly needed.
- Monitor daily — headers drift after hosting migrations or platform updates.
- Sell as "stay A-grade in Mozilla Observatory" — that's the language clients' buyers use.