Ankush Ananth Bhat
Ankush Ananth Bhat
Backend

Securing Your Express App with Helmet.js: A Practical Guide

HTTP headers are your app's first line of defense. Helmet.js sets the right ones automatically — here's exactly what it does and why every Express app needs it.

Securing Your Express App with Helmet.js: A Practical Guide
Ankush

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.js or joi)
  • 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.