HTTP Security Headers: The Complete Checklist for Your Web App
Here’s a common audit finding. An application has solid fundamentals — an ORM to prevent SQL injection, rate limiting against brute force, HttpOnly cookies for sessions — and then a basic header check comes back with critical failures: Missing Content-Security-Policy. Missing HSTS. Missing X-Content-Type-Options.
The app serves correct HTML, but it ships responses with no browser-side protection, leaving the last line of defense unconfigured and exposing users to XSS, clickjacking, and protocol-downgrade attacks. The fix usually takes an afternoon in an Nginx config — which is exactly why it’s worth doing before an auditor finds it.
HTTP security headers are the highest-ROI security work in web development. No migrations, no refactoring, no architecture changes — just text strings on your server’s responses that tell the browser how to behave.
This guide covers the six HTTP security headers worth deploying to production.
Audit your production headers immediately. Paste your website URL into our HTTP Header Analyzer. It executes a real-time scan of your server responses and generates a strict security posture score with actionable fixes.
Why HTTP Headers Matter
When a user’s browser (Chrome, Firefox, Safari) connects to your server and downloads your HTML, the browser operates under extremely permissive, legacy-compatible defaults. If your server does not explicitly instruct the browser to restrict behavior, the browser will:
- Willingly execute any JavaScript it finds, even if it was injected maliciously (XSS).
- Allow malicious websites to render your application inside an invisible
<iframe>to steal user clicks (Clickjacking). - Guess the execution type of uploaded files based on content, ignoring file extensions (MIME Sniffing).
Security headers act as a rigid rulebook. You are transmitting a contract to the browser, demanding that it operate in a strictly locked-down state.
1. Content-Security-Policy (CSP)
The Content-Security-Policy (CSP) is the most powerful browser-security header — and the hardest to configure without breaking your app.
A CSP is a whitelist. It tells the browser which domains are allowed to load JavaScript, CSS, fonts, images, and WebSockets. If an attacker injects <script src="https://evil-hacker.com/steal.js"></script> into your page (an XSS attack), the browser checks the CSP, finds that evil-hacker.com isn’t on the whitelist, and blocks the script.
The Architecture of a Strict CSP
Content-Security-Policy:
default-src 'self';
script-src 'self' https://trusted-cdn.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
connect-src 'self' https://api.mybackend.com;
frame-ancestors 'none';
Breaking Down the Directives:
default-src 'self': The ultimate fallback. If a specific resource type is not listed, the browser will only load it if it comes from the exact same domain as the HTML file.script-src: Restricts where JavaScript can be loaded from.style-src: Restricts CSS files. The'unsafe-inline'tag is often required for modern React/Vue libraries that inject CSS directly into the DOM, though it is technically a security reduction.img-src: Restricts images. Allowingdata:is common to permit Base64 encoded placeholder images.connect-src: Controls where the browser can sendfetch(),XMLHttpRequest, and WebSocket connections. If you forget to whitelist your API domain here, the browser will block the AJAX requests, generating errors that look identical to CORS failures.frame-ancestors: Restricts which websites are allowed to embed your page in an<iframe>.
The Implementation Strategy: Report-Only Mode
Do not deploy a strict CSP to production blindly; you will almost certainly break your own website by blocking legitimate third-party analytics or font loading.
Instead, deploy it in Report-Only mode.
Content-Security-Policy-Report-Only: default-src 'self'; report-uri https://api.yoursite.com/csp-reports
In this mode, the browser does not block any content. Instead, every time a violation occurs, the browser silently sends an automated JSON report to the specified report-uri. Monitor these logs for a week, adjust your whitelist to accommodate legitimate traffic, and only then flip the switch to enforcement.
The Cryptographic Nonce Approach
If you absolutely must use inline <script> tags, do not use the 'unsafe-inline' directive. Instead, generate a cryptographically random, single-use nonce on the server for every HTTP request.
Content-Security-Policy: script-src 'nonce-8fB1xGq4'
<!-- The browser matches the nonce and executes the script -->
<script nonce="8fB1xGq4">
initializeDashboard();
</script>
<!-- The attacker injected this script, but they cannot guess the nonce. The browser blocks it. -->
<script>
stealCookies();
</script>
2. Strict-Transport-Security (HSTS)
HTTPS is mandatory in 2026. However, if a user types yourwebsite.com into their address bar, the browser initially connects over unencrypted HTTP (Port 80) before your server can issue a 301 Redirect to the encrypted HTTPS version.
During that initial HTTP connection, an attacker on a public Wi-Fi network can perform an SSL Stripping Man-in-the-Middle Attack, intercepting the request and permanently preventing the user from reaching the secure HTTPS version.
Strict-Transport-Security (HSTS) permanently solves this by caching a directive in the user’s browser.
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
The Parameters:
max-age=31536000: Instructs the browser to remember this rule for exactly one year (in seconds). For the next 365 days, if the user attempts to load the HTTP version, the browser will instantly, internally upgrade the request to HTTPS before a single packet leaves the computer.includeSubDomains: Applies this ironclad rule toapi.,blog., andapp.subdomains.preload: Indicates that you want your domain hardcoded into the source code of Chrome, Firefox, and Safari via the official HSTS Preload List.
Warning: Submitting to the HSTS Preload List is a one-way street. If you ever deploy an internal subdomain that lacks a valid SSL certificate, it will be completely inaccessible globally.
3. X-Content-Type-Options
This is the simplest, most indisputable header on the list.
X-Content-Type-Options: nosniff
The Vulnerability: Historically, browsers tried to be “helpful.” If your server transmitted a file labeled Content-Type: text/plain, but the browser scanned the file and detected JavaScript code inside, the browser would ignore the server’s label and execute the JavaScript anyway. This is called MIME Sniffing.
Attackers exploit this by uploading a malicious script disguised as a profile picture (avatar.jpg). If the server serves the image, and the browser sniffs the contents, it executes the payload.
The nosniff directive commands the browser to strictly honor the Content-Type header provided by the server. There is absolutely no modern scenario where this header should be omitted.
4. X-Frame-Options (Clickjacking Defense)
Clickjacking is a UI redress attack. An attacker builds a page (“Win a Free iPhone!”) and places an invisible, zero-opacity <iframe> of your site’s “Delete Account” or “Transfer Funds” button behind the “Claim Prize” button.
When the user clicks “Claim Prize,” their click passes through the transparent layer and hits your application. Because they are authenticated, the destructive action succeeds.
X-Frame-Options: DENY
The Directives:
DENY: Your website cannot be framed by anyone, including yourself.SAMEORIGIN: Your website can only be framed by pages on your exact domain.
Note: While X-Frame-Options is technically legacy and has been superseded by CSP’s frame-ancestors directive, you must include both to ensure compatibility with older browser engines.
5. Referrer-Policy
When a user clicks a link on your website to navigate to an external site, the browser sends a Referer header to the destination server, revealing exactly what URL the user came from.
If your web application utilizes URL parameters for session tokens, password reset keys, or private search queries (e.g., https://myapp.com/reset?token=123ABC), clicking an external link will leak that highly sensitive token to the destination server’s analytics logs.
Referrer-Policy: strict-origin-when-cross-origin
The Strategy:
The strict-origin-when-cross-origin policy is the gold standard.
- If the user navigates within your own domain (Same-Origin), the full URL path is sent.
- If the user clicks an external link (Cross-Origin), the browser strips the path and query parameters, sending only the root domain (
https://myapp.com/). The external site knows where traffic came from, but your sensitive tokens remain secure.
6. Permissions-Policy (Feature Policy)
Modern web browsers have access to sensitive hardware: Webcams, Microphones, GPS Geolocation, and Payment APIs.
If your application does not need the camera, but a third-party advertising script embedded on your page decides to silently activate the camera, your user’s privacy is compromised. The Permissions-Policy allows you to explicitly revoke hardware access at the browser level.
Permissions-Policy: camera=(), microphone=(), geolocation=(self), payment=()
The empty parentheses () mean “Access Denied for Everyone, including my own code.” The (self) directive means “Only my domain can access this hardware; third-party iframes cannot.”
Implementation Across Modern Frameworks
Implementing these headers requires altering the response pipeline of your backend architecture.
1. Node.js / Express (using Helmet)
The helmet package is the industry standard for securing Express applications.
const express = require('express');
const helmet = require('helmet');
const app = express();
// Helmet automatically configures HSTS, X-Frame-Options,
// X-Content-Type-Options, and Referrer-Policy
app.use(helmet());
// You must configure CSP manually based on your asset architecture
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'nonce-SERVER_GENERATED_NONCE'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https://images.example.com"]
}
}));
2. Next.js (next.config.js)
Next.js allows you to inject headers directly into the routing layer without a custom server.
// next.config.js
const securityHeaders = [
{ key: 'X-DNS-Prefetch-Control', value: 'on' },
{ key: 'Strict-Transport-Security', value: 'max-age=31536000; includeSubDomains; preload' },
{ key: 'X-Frame-Options', value: 'SAMEORIGIN' },
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
{ key: 'Content-Security-Policy', value: "default-src 'self'; script-src 'self';" }
];
module.exports = {
async headers() {
return [
{
source: '/(.*)', // Apply to all routes
headers: securityHeaders,
},
]
},
}
3. Nginx Reverse Proxy
If you manage your own infrastructure, enforcing headers at the Nginx level ensures that every microservice behind the proxy is uniformly protected.
server {
listen 443 ssl http2;
server_name myapp.com;
# HSTS
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# MIME Sniffing Protection
add_header X-Content-Type-Options "nosniff" always;
# Clickjacking Protection
add_header X-Frame-Options "SAMEORIGIN" always;
# Referrer Leakage Protection
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Content Security Policy
add_header Content-Security-Policy "default-src 'self';" always;
location / {
proxy_pass http://localhost:3000;
}
}
Frequently Asked Questions
Q: Will deploying HSTS break my local development environment?
Yes. If you deploy HSTS with includeSubDomains and test it on localhost, Chrome will force https://localhost permanently. You will have to dig into Chrome’s hidden chrome://net-internals/#hsts menu to clear the domain policy. Never send HSTS headers in non-production environments.
Q: Do I need X-XSS-Protection?
No. The X-XSS-Protection header is officially deprecated. Modern browsers removed support for it because it frequently introduced worse vulnerabilities than it solved. Rely entirely on a strict CSP instead.
Q: How do I handle inline styles if CSP blocks them?
The most secure method is to extract all inline styles (like <div style="color: red;">) into external CSS classes. If you are using a UI framework that requires inline styles dynamically, you must append 'unsafe-inline' to the style-src directive, accepting the minor security trade-off.
Further Reading
- CORS Errors Explained: Fetch Failures and Preflight Requests
- XSS Prevention: The Developer Guide to HTML Entity Encoding
- SSL/TLS Certificates: Cryptography and Handshakes
Do not guess your security posture. Paste your application URL into our HTTP Header Analyzer for an instant, comprehensive security audit. If you are configuring API calls, use the cURL to Code Converter to safely integrate secure endpoints.