Security headers are instructions your server sends with every page that tell the browser what it may and may not do: only connect over HTTPS, only run scripts from approved origins, never let another site frame the page, and so on. They cost nothing, need no code changes, and close the gaps most automated scanners and security questionnaires look for first.
This is the checklist we use on client sites and on our own Angular site, including the mistakes we made rolling it out. Run your domain through the free ZetsApp Website Security Audit before and after, so you can see the grade move.
The 8 security headers at a glance
| Header | Blocks | Recommended value | Risk of breaking the site |
|---|---|---|---|
| Strict-Transport-Security | HTTPS downgrade and cookie theft on public Wi-Fi | max-age=63072000; includeSubDomains; preload | Low, once every subdomain has HTTPS |
| Content-Security-Policy | Cross-site scripting (XSS), data exfiltration, rogue third-party scripts | Site-specific allow-list | High: test in report-only first |
| X-Frame-Options / frame-ancestors | Clickjacking | SAMEORIGIN and frame-ancestors 'self' | Low, unless partners embed you |
| X-Content-Type-Options | MIME sniffing (uploads executed as script) | nosniff | Very low |
| Referrer-Policy | Leaking full URLs (tokens, search terms) to other sites | strict-origin-when-cross-origin | Very low |
| Permissions-Policy | Embedded frames requesting camera, microphone, location, payment | camera=(), microphone=(), geolocation=() | Low: allow features you actually use |
| Cross-Origin-Opener-Policy | Cross-window attacks and XS-Leaks | same-origin-allow-popups | Low; plain same-origin can break OAuth and payment pop-ups |
| Cross-Origin-Resource-Policy | Other sites embedding your files (Spectre-style leaks) | same-site | Medium if other domains hotlink your images |
You can drop X-XSS-Protection. Modern browsers removed the filter it controlled, and the old filter could itself be abused. If you still send it, set it to 0.
The rollout order that won't break production
- Day 1, zero-risk headers: X-Content-Type-Options, Referrer-Policy, X-Frame-Options, Permissions-Policy. Deploy, re-scan, done.
- Day 1, HSTS with a short max-age: start at
max-age=86400, confirm every subdomain serves HTTPS, then raise it to two years and addincludeSubDomains; preload. Preloading is hard to undo, so only submit to hstspreload.org when you are sure. - Week 1, CSP in report-only: send
Content-Security-Policy-Report-Onlywith your best-guess policy and read the violations in the browser console (or collect them withreport-to). Add each legitimate origin you find. - Week 2, enforce CSP: switch the header name to
Content-Security-Policy. Keep the report-only header alongside it with a stricter draft for the next iteration. - Then COOP and CORP: check that sign-in pop-ups (Google, Microsoft, Stripe, PayPal) still work.
Writing a Content-Security-Policy that survives real life
CSP is the header with the most protection and the most breakage. A practical starting policy for a marketing or SaaS site:
default-src 'self';
script-src 'self' https://cdn.jsdelivr.net;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
font-src 'self' https://fonts.gstatic.com;
img-src 'self' data: https:;
connect-src 'self' https://api.yourdomain.com;
frame-src https://www.youtube-nocookie.com;
object-src 'none'; base-uri 'self'; form-action 'self';
frame-ancestors 'self'; upgrade-insecure-requestsFour lessons from hardening our own site:
- Inventory before you write. We grepped the codebase and our CMS content for every external host. The surprise was not the CDN scripts we knew about but a browser-side call to an AI API buried in one tool, plus scripts loaded on demand by two components.
- Same-origin iframes inherit the parent's CSP. Blog posts we render inside
srcdoc/document.writeiframes run inline scripts. A policy without'unsafe-inline'silently broke every one of them, even though the iframe looked separate. Either move that content to its own origin or accept'unsafe-inline'for now and plan the migration. - A hash or nonce disables
'unsafe-inline'. If you add'sha256-…'for one inline snippet, browsers ignore'unsafe-inline'for everything else. Go all-in on hashes or nonces, or do not use them at all. - Remove
eval()instead of allowing it. A calculator on our site usedevalon user input. Replacing it with a 120-line whitelist parser let us drop'unsafe-eval'and closed a self-XSS hole at the same time.
Copy-paste configs by platform
Nginx
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;The always flag matters: without it Nginx omits the headers on 4xx and 5xx responses.
Apache (.htaccess, mod_headers)
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Referrer-Policy "strict-origin-when-cross-origin"Vercel (vercel.json)
{ "headers": [ { "source": "/(.*)", "headers": [
{ "key": "X-Content-Type-Options", "value": "nosniff" },
{ "key": "X-Frame-Options", "value": "SAMEORIGIN" } ] } ] }On Vercel, vercel.json must sit in the root of the deployed folder. If you upload a pre-built dist folder, a vercel.json in the project root is ignored. This exact mistake kept our own headers from going live for weeks.
IIS / ASP.NET Core
Add headers in middleware with context.Response.OnStarting, call app.UseHsts() outside development, set AddServerHeader = false on Kestrel, and remove X-Powered-By in web.config under httpProtocol/customHeaders.
Headers are half the job: cookies, DNS and email
- Cookies: session cookies need
Secure; HttpOnly; SameSite=Lax. A missingSecureflag on an HTTPS site counts as a failure in most audits. - SPF and DMARC: without an enforced DMARC policy (
p=quarantineorp=reject) anyone can send phishing that appears to come from your domain. Our audit flags this on most small-business sites we scan. - security.txt: publish
/.well-known/security.txtwith a contact address and expiry date (RFC 9116). Note that some static-site build tools skip dot-folders when copying, so check that the file is actually deployed. - Subresource Integrity: pin versioned CDN scripts with
integrity="sha384-…"so a compromised CDN cannot inject code.
How to verify
Run the ZetsApp Website Security Audit after each step. It reads your live headers, certificate, cookies and DNS, grades the site A+ to F, and generates the missing headers for Nginx, Apache, IIS, Vercel, Netlify or Cloudflare in one block. It also produces a branded PDF you can attach to a client handover or a vendor security questionnaire.
If you would rather have it done for you, our cloud and DevOps team hardens headers, TLS and email authentication, usually within a day. Ask for a hardening quote.
Frequently asked questions
What are the most important HTTP security headers?
Strict-Transport-Security (forces HTTPS), Content-Security-Policy (limits which scripts can run), X-Frame-Options or CSP frame-ancestors (stops clickjacking) and X-Content-Type-Options: nosniff. Referrer-Policy and Permissions-Policy are quick wins on top.
Do security headers affect SEO?
Not directly as a ranking factor, but HTTPS is a confirmed signal, and headers like HSTS and a working CSP protect against injected spam links and malware that can get a site flagged or deindexed.
Will a Content-Security-Policy break my website?
It can, if it omits an origin your pages use. Deploy it first as Content-Security-Policy-Report-Only, fix every legitimate violation, and only then switch to the enforcing header.
Is X-XSS-Protection still needed?
No. Browsers removed the XSS auditor it controlled. Set it to 0 or remove it, and rely on a Content-Security-Policy instead.
How do I check my website's security headers?
Use a scanner such as the free ZetsApp Website Security Audit, which reads your live headers and grades them, or open your browser developer tools, select the page request in the Network tab and read the response headers.


