NodeJSSecuring Headers with Helmet

Securing Headers with Helmet

HTTP response headers are a cheap but powerful security lever. Headers like Content-Security-Policy, X-Frame-Options, and Strict-Transport-Security tell browsers how to behave when rendering your responses — blocking inline scripts from XSS payloads, refusing to render your page in an iframe, and requiring HTTPS. Helmet is an Express middleware that sets a sensible default for all of them in one call. This page explains what each header does, how to configure Helmet, and the one that requires genuine thought: Content Security Policy.

Install and use

Bash
npm install helmet

JS
import helmet from 'helmet'
import express from 'express'

const app = express()
app.use(helmet())    // sets 11 security headers with safe defaults
One line sets 11 security headers — register Helmet before your routes
`helmet()` is a collection of smaller middleware functions, each setting one header. Calling `helmet()` enables all of them with sensible defaults. Register it early in your middleware chain — before routes — so every response, including error responses, gets the headers. You can opt individual plugins in or out and configure them, but the defaults are correct for most apps.
What Helmet sets

Header

What it does

Content-Security-Policy

Controls which scripts/styles/frames are allowed — primary XSS mitigation

X-Frame-Options

Blocks your page from being embedded in an iframe (clickjacking)

Strict-Transport-Security

Tells browsers to always use HTTPS (HSTS)

X-Content-Type-Options

Prevents MIME-type sniffing

Referrer-Policy

Controls how much URL info is sent in the Referer header

Permissions-Policy

Disables unused browser features (camera, geolocation, etc.)

X-DNS-Prefetch-Control

Prevents DNS prefetching that leaks browsing intent

Cross-Origin-Opener-Policy

Isolates browsing context to prevent cross-origin info leaks

Content Security Policy — the powerful one

JS
app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],                        // fallback: only same origin
      scriptSrc:  ["'self'", 'cdn.jsdelivr.net'],    // allow scripts from CDN
      styleSrc:   ["'self'", "'unsafe-inline'"],     // inline styles (avoid if possible)
      imgSrc:     ["'self'", 'data:', 'https:'],
      connectSrc: ["'self'", 'https://api.example.com'],
      fontSrc:    ["'self'", 'fonts.gstatic.com'],
      frameSrc:   ["'none'"],
      objectSrc:  ["'none'"],
      upgradeInsecureRequests: [],
    },
  },
}))
Helmet's default CSP may break your app — tune it for your actual sources
Helmet's default `Content-Security-Policy` blocks inline scripts and external resources, which often breaks apps that use CDN-hosted libraries, Google Fonts, or inline event handlers. Don't disable CSP — tune it. Use the **report-only** mode first (`Content-Security-Policy-Report-Only`) to see what would be blocked without enforcing it, then whitelist the actual origins your app needs. A CSP that blocks your own app is worse than no CSP because teams disable it entirely.
Report-only mode for tuning CSP

JS
app.use(helmet({
  contentSecurityPolicy: {
    useDefaults: true,
    reportOnly: true,          // logs violations instead of blocking them
    directives: {
      reportUri: '/csp-report',
    },
  },
}))

// Receive reports:
app.post('/csp-report', express.json({ type: 'application/csp-report' }), (req, res) => {
  console.warn('CSP violation:', req.body)
  res.status(204).end()
})
HSTS — Strict-Transport-Security

JS
app.use(helmet({
  strictTransportSecurity: {
    maxAge: 31_536_000,        // 1 year in seconds
    includeSubDomains: true,
    preload: true,             // submit to browser preload lists
  },
}))
Only enable HSTS if your entire domain is HTTPS — it locks out HTTP for the maxAge
HSTS tells browsers to **never** connect to your domain over HTTP again for `maxAge` seconds. If you set a year-long HSTS and then need to temporarily serve HTTP (e.g. a lapsed certificate), browsers will refuse to connect — users see an error they can't bypass. Enable HSTS only when HTTPS is fully deployed, stable, and you're confident it will stay that way. Start with a short `maxAge` (e.g. 3600) while testing, then increase it once you're sure.
Disabling or configuring individual middleware

JS
// Disable one: pass false for that key:
app.use(helmet({
  frameguard: false,            // if you legitimately need iframes
}))

// Configure one:
app.use(helmet({
  referrerPolicy: { policy: 'same-origin' },
  xFrameOptions: { action: 'sameorigin' },  // allow framing by same origin
}))
Verifying your headers
  • Use securityheaders.com to grade your live site's headers.

  • Run curl -I https://yourapp.com to inspect headers from the CLI.

  • browser DevTools → Network → response headers on any request.

  • In CI: add a test that asserts key headers are present on responses.

Next
Clean user input before it reaches your code: [Input Sanitization](/nodejs/input-sanitization).