
Ankush Ananth Bhat
18 December 2025
Why HTTP Headers Matter for Security
When your Express server responds to a request, it sends back more than just HTML or JSON — it sends HTTP headers. Most developers focus on status codes and body content, but headers carry powerful security directives that browsers respect and attackers probe.
Without the right headers, your app is silently vulnerable to:
- Cross-Site Scripting (XSS) attacks injecting malicious scripts
- Clickjacking — your UI loaded invisibly inside another site's iframe
- MIME-type sniffing — browsers misinterpreting file types and executing malicious content
- Information leakage — telling attackers exactly what stack you're running
Fixing all of this manually is tedious and error-prone. That's where Helmet.js comes in.
What Is Helmet.js?
Helmet is a collection of small Express middleware functions, each responsible for setting (or removing) a specific HTTP security header. Install it once, use it in one line, and your app immediately becomes significantly harder to attack.
npm install helmet
const express = require('express');
const helmet = require('helmet');
const app = express();
// That's it. Helmet's defaults kick in immediately.
app.use(helmet());
Under the hood, calling helmet() activates 14 individual middleware — each targeting a different attack vector.
What Helmet Sets By Default
Let's break down exactly what helmet() does out of the box.
1. Content-Security-Policy (CSP)
Protects against: XSS, data injection attacks
CSP is the heavyweight of security headers. It tells the browser which sources are allowed to load scripts, styles, images, fonts, and other resources. Anything not on the whitelist gets blocked.
Content-Security-Policy: default-src 'self'; script-src 'self'; ...
Helmet sets a strict default CSP. In production, you'll likely need to customize this for your CDNs and third-party scripts — but starting strict and loosening is far safer than the reverse.
app.use(
helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "https://cdn.jsdelivr.net"],
imgSrc: ["'self'", "data:", "https://res.cloudinary.com"],
},
})
);
2. X-Frame-Options
Protects against: Clickjacking
Clickjacking loads your site invisibly inside an <iframe> on an attacker's page. The user thinks they're clicking your button — they're actually clicking theirs.
X-Frame-Options: SAMEORIGIN
This tells the browser: only allow this page to be framed by pages on the same origin. Helmet sets this by default.
3. X-Content-Type-Options
Protects against: MIME-type sniffing
Older browsers would "sniff" a response body and guess its content type, even if the server declared it explicitly. Attackers exploit this by uploading a file that looks like an image but is actually executable JavaScript.
X-Content-Type-Options: nosniff
With nosniff, the browser strictly follows the declared Content-Type and won't try to second-guess it.
4. Strict-Transport-Security (HSTS)
Protects against: Protocol downgrade attacks, cookie hijacking over HTTP
HSTS tells the browser: always connect to this site over HTTPS, even if the user types http://. Once a browser sees this header, it will refuse to connect over plain HTTP for the duration specified.
Strict-Transport-Security: max-age=15552000; includeSubDomains
Only enable HSTS on production apps with a working SSL certificate. Enabling it on HTTP-only dev environments will lock browsers into HTTPS for months.
5. X-DNS-Prefetch-Control
X-DNS-Prefetch-Control: off
Browsers speculatively pre-resolve DNS for links on a page. This can leak information about what your page links to. Helmet disables it by default.
6. Referrer-Policy
Protects against: Leaking internal URLs to third parties
When a user clicks a link to an external site, the browser sends a Referer header with the full URL of your page. This can expose internal paths, query parameters, or session tokens.
Referrer-Policy: no-referrer
Helmet sets no-referrer by default, which strips the header entirely on cross-origin requests.
7. X-Powered-By — Removed
By default, Express adds this header to every response:
X-Powered-By: Express
This hands attackers a starting point: they know your stack, and they know which vulnerabilities to probe. Helmet removes it silently.
The Full Picture: All 14 Middleware
| Middleware | What It Does |
|---|---|
contentSecurityPolicy |
Restricts resource loading sources |
crossOriginEmbedderPolicy |
Controls cross-origin embedding |
crossOriginOpenerPolicy |
Isolates browsing context |
crossOriginResourcePolicy |
Controls cross-origin resource sharing |
dnsPrefetchControl |
Disables DNS prefetching |
expectCt |
Enforces Certificate Transparency (deprecated in newer Helmet) |
frameguard |
Prevents clickjacking via iframes |
hidePoweredBy |
Removes X-Powered-By |
hsts |
Forces HTTPS connections |
ieNoOpen |
Prevents IE from executing downloads in your site's context |
noSniff |
Disables MIME-type sniffing |
originAgentCluster |
Enables origin-keyed agent clustering |
permittedCrossDomainPolicies |
Restricts Adobe Flash/PDF cross-domain requests |
referrerPolicy |
Controls the Referer header |
xssFilter |
Sets legacy X-XSS-Protection header (mostly historical) |
Customizing Helmet
You don't have to accept all defaults. You can disable specific middleware or pass configuration to each:
app.use(
helmet({
// Disable HSTS (e.g., during local development)
hsts: false,
// Custom referrer policy
referrerPolicy: { policy: "strict-origin-when-cross-origin" },
// Customize frameguard
frameguard: { action: "deny" }, // disallow ALL framing, even same-origin
})
);
Or use individual middleware directly for fine-grained control:
// Only apply CSP and frameguard manually
app.use(helmet.contentSecurityPolicy({ /* ... */ }));
app.use(helmet.frameguard({ action: 'sameorigin' }));
Helmet in a Real Express App
Here's a production-ready setup combining Helmet with a few other security essentials:
const express = require('express');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const cors = require('cors');
const app = express();
// Security headers
app.use(helmet());
// CORS — be explicit about allowed origins
app.use(cors({
origin: process.env.CLIENT_URL,
methods: ['GET', 'POST', 'PUT', 'DELETE'],
credentials: true,
}));
// Rate limiting — prevent brute-force and DDoS
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100,
});
app.use('/api/', limiter);
// Parse JSON with a size limit
app.use(express.json({ limit: '10kb' }));
app.get('/api/health', (req, res) => {
res.json({ status: 'ok' });
});
app.listen(3000);
What Helmet Doesn't Do
Helmet is not a silver bullet. It handles HTTP headers — that's its lane. It does not:
- Sanitize user input (use
validator.jsorjoi) - Prevent SQL injection (use parameterized queries or an ORM)
- Manage authentication (use
passport, JWTs, or sessions carefully) - Rate-limit requests (use
express-rate-limit) - Scan for vulnerabilities in dependencies (use
npm audit)
Think of Helmet as your browser-facing shield — one essential layer in a proper defense-in-depth strategy.
Verifying Your Headers
After deploying, check your headers at:
- securityheaders.com — Grades your response headers A through F
- Browser DevTools → Network tab → click any request → Headers section
curl -I https://yourdomain.com— Quick terminal check
A well-configured Express + Helmet app should score an A or A+ on securityheaders.com.
Summary
| Without Helmet | With Helmet |
|---|---|
X-Powered-By: Express exposed |
Header removed |
| No clickjacking protection | X-Frame-Options: SAMEORIGIN set |
| MIME sniffing enabled | nosniff directive active |
| No CSP | Strict default CSP applied |
| HTTP downgrade possible | HSTS enforced |
Three lines of code — npm install helmet, require('helmet'), app.use(helmet()) — and your Express app goes from an open invitation to a hardened target.
Security isn't about being unbreakable. It's about making the cost of an attack higher than the reward. Helmet raises that cost, for free, in seconds.
Building an Express API and want a security audit checklist? Drop an email — I've got a template worth sharing.
