HTTP Security Headers: A Practical Guide to CSP, HSTS, and Frame Protection
· 10 min read
A practical guide to HTTP security headers: CSP, HSTS, X-Frame-Options, Referrer-Policy and how to verify each one.

HTTP security headers are response headers a server sends to tell a browser how to handle a page. They sit between your code and the visitor's browser, and they enforce rules the browser follows without asking the visitor for permission. A missing header is not a bug report waiting to happen; it is a control that never arrived, and the browser defaults to its own, more permissive behaviour.
This guide covers seven headers: Content-Security-Policy, Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, and the deprecated X-XSS-Protection. For each one, you will learn what it does, where administrators misconfigure it, and how to verify it is actually present on your pages.
How HTTP security headers work
The browser receives an HTTP response before it renders anything. Security headers ride along in that response. When the browser sees Content-Security-Policy, it applies the policy to every resource the page tries to load. When it sees Strict-Transport-Security, it upgrades future visits from HTTP to HTTPS automatically. These are enforcement mechanisms the browser applies itself, not signals it asks the server to reconsider.
Headers are not a substitute for fixing vulnerabilities. A CSP policy does not repair a cross-site scripting hole in your template; it limits what an attacker can do with one. HSTS does not fix a broken certificate; it prevents the visitor from bypassing a certificate warning. Treat them as a layer on top of secure code, not a replacement for it.
Content-Security-Policy: what loads on your page
Content-Security-Policy controls which resources the user agent may load for a page, guarding against cross-site scripting (XSS). You write it as one or more directives: script-src governs JavaScript, style-src covers stylesheets, img-src handles images, frame-ancestors controls framing, default-src sets the fallback, and report-uri or report-to sends violation reports somewhere you can read them. The specification is CSP Level 3 (W3C CSP Level 3).
The most common mistake is unsafe-inline. When you allow inline scripts, you defeat the policy's main purpose: an attacker who injects a script tag into your page can still run code. Another frequent error is deploying an enforced policy without first testing it in report-only mode. If your policy blocks a resource your page needs, the page breaks silently in some browsers and loudly in others, and you will not know why unless you are watching violation reports.
The fix is to start with Content-Security-Policy-Report-Only. That header tells the browser to report violations without enforcing them. Watch your reporting endpoint, adjust the policy until reports stop, then switch to the enforced header. This is the standard rollout path because it lets you break things safely.
Strict-Transport-Security: HTTPS only
Strict-Transport-Security (HSTS) tells browsers to only access a host over HTTPS and to auto-upgrade any HTTP link to HTTPS. The required directive is max-age, measured in seconds. includeSubDomains extends the policy to every subdomain. preload submits the domain to browser preload lists, which enforce HSTS even on the very first visit (MDN HSTS, RFC 6797).
For preloading, max-age must be at least 31536000 (about one year), and includeSubDomains must be present. A two-year value is 63072000. The header only counts when it arrives over HTTPS; browsers ignore HSTS sent over plain HTTP. Setting max-age=0 disables HSTS immediately.
The operational risk is preload removal. Once your domain is on a browser preload list, removing it is slow — months in some browsers. If you add preload before you are certain your HTTPS setup is stable, you can lock visitors out when a certificate expires or a subdomain cannot serve HTTPS. Set max-age without preload first. Confirm every subdomain works over HTTPS. Then add includeSubDomains. Only then add preload.
X-Frame-Options and clickjacking
X-Frame-Options tells the browser whether it may render the document in a frame, iframe, embed, or object. The goal is to prevent clickjacking, where a malicious site frames your page and tricks a visitor into clicking something they cannot see. DENY blocks all framing. SAMEORIGIN allows framing only by pages on the same origin (MDN X-Frame-Options, WHATWG HTML).
ALLOW-FROM is obsolete. Modern browsers ignore the entire header if they encounter it, so a policy built on ALLOW-FROM protects no one. CSP's frame-ancestors directive is broader than X-Frame-Options, and it supports multiple origins. An X-Frame-Options value inside a meta tag has no effect — this is an HTTP header only.
Do you need both X-Frame-Options and CSP frame-ancestors? If you send CSP frame-ancestors, modern browsers ignore X-Frame-Options for that decision. You can keep X-Frame-Options as a fallback for older browsers, but do not rely on it as your only defence.
X-Content-Type-Options: nosniff
X-Content-Type-Options: nosniff tells the browser to respect the declared Content-Type and not MIME-sniff. For script and style destinations, the browser blocks the response if the MIME type does not match — a JavaScript file served as text/plain will not run. For other types, such as text/plain, the browser uses the declared type instead of interpreting the content as HTML. That last point prevents an attack where user-uploaded content is served as plain text but the browser renders it as HTML anyway (MDN X-Content-Type-Options, WHATWG Fetch).
The misconfiguration here is simple omission. Many servers send no X-Content-Type-Options header at all, and the browser falls back to sniffing. If you host user uploads, send nosniff and serve the uploads with a strict Content-Type such as text/plain or application/octet-stream. This header does not stop every attack, but it removes a common bypass.
Referrer-Policy: how much referrer information leaves
Referrer-Policy controls how much referrer information (the Referer header) travels with requests. There are eight directives: no-referrer, no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, and unsafe-url (MDN Referrer-Policy, W3C Referrer Policy).
The browser default changed in November 2020. When no policy is set or the value is invalid, browsers now use strict-origin-when-cross-origin instead of the older no-referrer-when-downgrade. On an HTTPS-to-HTTP navigation, strict-origin-when-cross-origin sends only the origin, not the full path. That means the destination learns https://yoursite.com/ but not https://yoursite.com/private-page.
When should you override the default? Use no-referrer on pages where leaking the origin itself is a problem — for example, a password-reset page that links to an external support site. Use same-origin when you need full referrer data for your own analytics but nothing external. Avoid unsafe-url: it sends the full URL, including path and query string, to every destination, even from HTTPS to HTTP. That leaks internal URLs to insecure origins.
Permissions-Policy: camera, microphone, and more
Permissions-Policy (the successor to Feature-Policy) allows or denies browser capabilities in a document or iframe. Directives include camera, geolocation, microphone, autoplay, fullscreen, payment, and usb. Allowlist values are * (all origins), () (disabled), self, or a specific origin (MDN Permissions-Policy, W3C Permissions Policy).
MDN marks this header as Limited availability and Experimental. It is not part of browser Baseline, so it does not work in all widely-used browsers. An unsupported browser simply ignores the header — there is no fallback to configure. Whether it is worth deploying depends on your site. If you embed third-party iframes and want to block their camera or microphone access, this header expresses that intent. For most sites, the practical effect today is limited by browser support.
X-XSS-Protection: deprecated
X-XSS-Protection is deprecated and non-standard. MDN states it is not part of any specification or draft. It was an Internet Explorer, Chrome, and Safari feature that blocked pages on detected reflected XSS. MDN warns it can create XSS vulnerabilities in otherwise safe sites, and it is largely unnecessary when a strong CSP disables unsafe-inline (MDN X-XSS-Protection).
Values include 0, 1, 1; mode=block, and 1; report= (Chromium only). The recommendation is to not send it at all. Rely on Content-Security-Policy instead. If you find this header on your pages, remove it unless you have a specific reason to keep it.
How to verify headers are actually sent
You can check headers three ways, and they do not always agree.
curl -I https://yoursite.com/ fetches the headers for the root URL. It is fast and scriptable, but it shows one response at one moment. If your server sends different headers for different paths, curl -I on the homepage will not reveal it.
Browser DevTools show what a real browser actually received. Open the Network tab, reload the page, and click on the document request. The response headers panel lists every header the server sent, exactly as the browser saw it. This is the most accurate check, but it requires a browser and a manual look.
Online scanners crawl your site and report missing or misconfigured headers. They are convenient for a quick audit, but they may cache an old response, scan a path that behaves differently from your homepage, or miss headers that appear only after a cookie or login state.
A header that appears in curl but not in DevTools may be gated behind user-agent detection or a redirect. A header in DevTools but not in a scanner may mean the scanner cached an older response. Check both, and check more than one URL.
Common misconfigurations that make headers ineffective
Several mistakes recur across these headers, and each one neutralises the protection:
- unsafe-inline in CSP defeats protection against injected scripts.
- HSTS sent over HTTP is ignored by browsers; the header only counts over HTTPS.
- Missing includeSubDomains leaves subdomains unprotected even when the root domain has HSTS.
- Referrer-Policy unsafe-url leaks HTTPS URL paths to insecure origins.
- X-Frame-Options ALLOW-FROM is ignored by modern browsers.
- X-XSS-Protection can introduce XSS where none existed before.
OWASP maintains the Secure Headers Project, which documents response headers applications can use to increase security and restrict browsers from easily preventable vulnerabilities. The project is production-class, Apache 2.0 licensed, with docs, a validator, and statistics sub-projects (OWASP Secure Headers Project).
Security headers work best when you treat them as a checklist: pick the headers that apply to your site, test each one in report-only or non-enforcing mode first, verify with DevTools, and re-check after any server or CDN change. The header is only useful if it is actually being sent.
Related Tools & Further Reading
- CORS preflight failed: causes, fixes, and four myths — CORS is often confused with security headers; this guide explains where CORS ends and headers begin.
- Counterfeit TLS certificates: what the ccTLD hijacks prove — HSTS works with certificates; this piece explains what a valid certificate actually proves.
- Unable to get local issuer certificate: what it means — certificate errors that HSTS can make harder to ignore, and how to read them.
FAQ
Which security headers actually stop attacks versus which are just hygiene? X-XSS-Protection is deprecated and can introduce XSS in otherwise safe sites, while X-Content-Type-Options only mitigates MIME-sniffing for script and style destinations. Content-Security-Policy and HSTS deliver real exploit mitigation when configured correctly. The remaining headers are defence-in-depth: useful layers, but limited in practical effect when deployed on their own today.
What is the safe rollout order for Content-Security-Policy? Start with Content-Security-Policy-Report-Only so the browser reports violations without enforcing them. Watch your reporting endpoint, adjust the policy until reports stop, then switch to the enforced header. Testing first prevents broken pages, since an enforced policy can block scripts, styles, or images your layout depends on.
Do you need both CSP frame-ancestors and X-Frame-Options? If you send CSP frame-ancestors, modern browsers ignore X-Frame-Options for the framing decision. Keep X-Frame-Options as a fallback for older browsers, but do not rely on it as your only defence. CSP frame-ancestors supports multiple origins and is broader than ALLOW-FROM ever was on its own.
What is the HSTS preload risk? Preload requires max-age of at least 31536000 seconds and includeSubDomains. Once your domain is on a browser preload list, removal is slow across browsers. If you preload before your HTTPS setup is stable, an expired certificate or an unready subdomain can lock visitors out for months.
Why might a header appear in one tool but not another? curl -I fetches headers for one URL at one moment. Browser DevTools shows what a real browser received for the document request. Online scanners may cache an old response or scan a different path. A header gated behind user-agent detection appears in one tool but not another.
Does the default Referrer-Policy leak anything on HTTPS to HTTP? The browser default since November 2020 is strict-origin-when-cross-origin. On HTTPS to HTTP navigation it sends only the origin, not the full path. Override to no-referrer when leaking the origin itself is a problem, such as on password-reset pages or internal admin pages that link to external sites.
Is Permissions-Policy worth deploying? MDN marks Permissions-Policy as Limited availability and Experimental, and it is not part of browser Baseline. Unsupported browsers ignore the header entirely, with no fallback to configure. It matters most when you embed third-party iframes and want to block camera, geolocation, microphone, or payment access.
What mistakes recur across these headers? unsafe-inline in CSP defeats protection against injected scripts. HSTS sent over HTTP is ignored by browsers. Missing includeSubDomains leaves subdomains unprotected. Referrer-Policy unsafe-url leaks HTTPS paths. X-Frame-Options ALLOW-FROM is obsolete. X-XSS-Protection can introduce XSS where none existed before, so remove it unless you have a specific reason to keep it.

Written by
Abhay Khant
Hi, I'm Abhay Khant
I'm a developer, builder, and technology enthusiast interested in turning ideas into useful products.
I enjoy working across web development, cybersecurity, automation, SEO, and content creation. Rather than only learning technologies individually, I like putting them together to build things that people can use.
One of my main projects is Toolsura, a free online utility platform that brings practical tools into one place. I'm building Toolsura as a product and a learning project where I can explore development, infrastructure, SEO, content distribution, automation, and product growth.
What I Build
My work currently revolves around several areas:
Web development with Next.js, React, Tailwind CSS, and modern web technologies
Backend development with Go, APIs, databases, and server-side systems
Automation using n8n, APIs, Docker, and connected services
SEO and content systems for building organic search traffic
Cybersecurity including web security, bug bounty, SOC concepts, and malware analysis
Content creation including video editing, YouTube content, graphics, and promotional videos
I enjoy going beyond tutorials and experimenting with complete systems, from the frontend interface all the way to the backend, deployment, automation, and distribution.
Toolsura
Toolsura is one of the projects I'm most actively building.
The idea is simple: create useful, accessible online tools that solve small problems quickly.
Toolsura includes utilities for things such as text, JSON, URLs, PDFs, images, security checks, and other everyday technical tasks.
Building it has also become a practical way for me to learn how a modern web product works.
That means working on:
Product → Development → Infrastructure → SEO → Content → Distribution → Automation → Growth
Technology & Learning
I'm particularly interested in understanding how systems work rather than simply learning how to use them.
My development work has included technologies such as:
Next.js · React · TypeScript · Tailwind CSS · Go · Gin · GORM · PostgreSQL · MongoDB · Docker · RabbitMQ · MinIO · Strapi
I've also worked with Linux environments and tools such as Kali Linux, Termux, and related security tooling.
My cybersecurity interests include web application security, bug bounty research, SOC analysis, and malware analysis.
Building in Public
I also create content around the things I build and learn.
This includes:
Tutorials and technical articles
YouTube videos
Product demonstrations
Visual explainers
Promotional videos
SEO-focused educational content
My goal is to turn projects and experiments into useful resources that other people can learn from.
Why I Build
For me, building is one of the best ways to learn.
Instead of learning a technology in isolation, I prefer starting with a real problem and figuring out what is required to solve it.
Sometimes that means writing code. Other times it's debugging a deployment at midnight, or working out why a page isn't being indexed, why an API isn't behaving correctly, or how to automate something that would otherwise take hours.
Those problems are part of the process.
Every project gives me another opportunity to understand how technology works in the real world.
What I'm Working Toward
I'm continuing to develop my skills across software engineering, cybersecurity, automation, and digital products.
At the same time, I'm building Toolsura into a useful platform and experimenting with different ways to create, distribute, and grow technical products.
There is still a lot to learn, but that's exactly what makes building interesting.
Let's Connect
If you're interested in web development, cybersecurity, automation, SEO, online tools, or building products, feel free to connect.
I'm always interested in learning, experimenting, and building something useful.
Build. Learn. Improve. Repeat.