Skip to content
Express.js ex libraries 4 min read

helmet Security Headers

Browsers enforce a layer of security based on the HTTP response headers your server sends — but Express sets almost none of them by default. helmet closes that gap with a single line of middleware that applies a sensible set of protective headers, defending against cross-site scripting, clickjacking, MIME-type sniffing, and information leakage. It is a collection of smaller middlewares bundled together, each toggling one header, so you can keep the safe defaults or selectively customize and disable individual protections. This page covers the defaults, the headers they set, tuning the Content-Security-Policy, and turning specific protections off when they get in the way.

Installing and enabling helmet

helmet is a standalone package with no dependencies. Install it and register it with app.use() near the top of the middleware stack, before your routers, so every response that follows carries the headers.

npm install helmet
const express = require("express");
const helmet = require("helmet");

const app = express();

// Applies all of helmet's default protections to every response.
app.use(helmet());

app.get("/", (req, res) => {
  res.send("Hello, secure world");
});

app.listen(3000, () => console.log("Listening on http://localhost:3000"));

Calling helmet() with no arguments enables every default middleware. A response now includes a stack of security headers it would otherwise lack.

Output:

Content-Security-Policy: default-src 'self';base-uri 'self';font-src 'self' https: data:;...
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Resource-Policy: same-origin
Origin-Agent-Cluster: ?1
Referrer-Policy: no-referrer
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 0
X-DNS-Prefetch-Control: off
X-Download-Options: noopen
X-Permitted-Cross-Domain-Policies: none

What the default headers do

Each header addresses a specific class of attack. Understanding what they protect against helps you decide which ones to keep, tighten, or relax.

HeaderProtects againstDefault value
Content-Security-PolicyXSS, injection, untrusted resource loadingdefault-src 'self' plus directive set
Strict-Transport-SecurityProtocol downgrade / SSL strippingmax-age=31536000; includeSubDomains
X-Content-Type-OptionsMIME-type sniffingnosniff
X-Frame-OptionsClickjacking via framingSAMEORIGIN
Referrer-PolicyLeaking URLs to other sitesno-referrer
Cross-Origin-Resource-PolicyCross-origin resource theftsame-origin
X-XSS-ProtectionLegacy XSS auditor (now disabled)0
Origin-Agent-ClusterProcess-level origin isolation?1

X-XSS-Protection is deliberately set to 0. The old browser XSS auditor it controlled caused more vulnerabilities than it fixed and is gone from modern browsers — a strong Content-Security-Policy is the real defense.

Customizing the Content-Security-Policy

The CSP is the most powerful and the most likely to need tuning, because the default default-src 'self' blocks every external script, style, and font. Pass a contentSecurityPolicy option with your own directives to allow the origins your app legitimately uses. Directives you specify are merged over helmet’s defaults — pass useDefaults: false to start from an empty policy instead.

app.use(
  helmet({
    contentSecurityPolicy: {
      useDefaults: true,
      directives: {
        // Allow scripts from self plus a trusted CDN.
        "script-src": ["'self'", "https://cdn.jsdelivr.net"],
        "style-src": ["'self'", "'unsafe-inline'"],
        "img-src": ["'self'", "data:", "https://images.example.com"],
        "connect-src": ["'self'", "https://api.example.com"],
        // Block this site from being framed entirely.
        "frame-ancestors": ["'none'"],
      },
    },
  })
);

You can also configure CSP in report-only mode while you roll it out. The browser reports violations to an endpoint without actually blocking anything, so you can catch missing directives before they break the page.

app.use(
  helmet({
    contentSecurityPolicy: {
      reportOnly: true,
      directives: {
        "default-src": ["'self'"],
        "report-uri": ["/csp-report"],
      },
    },
  })
);

This sends a Content-Security-Policy-Report-Only header instead of the enforcing one.

Disabling specific protections

Some defaults conflict with legitimate use cases — for example, Cross-Origin-Resource-Policy: same-origin blocks a CDN from serving your images to other origins, and a strict CSP can break a third-party embed. Set the corresponding option to false to drop just that one middleware while keeping the rest.

app.use(
  helmet({
    // Disable CSP entirely (e.g. while debugging, or when a CDN sets it).
    contentSecurityPolicy: false,
    // Allow this app's resources to be loaded cross-origin.
    crossOriginResourcePolicy: false,
    // Permit framing from anywhere by removing X-Frame-Options.
    frameguard: false,
  })
);

You can also configure a single middleware on its own without the full bundle, which is useful when you only want one header or want different settings per route.

// HSTS only, with a longer max-age and preload flag.
app.use(
  helmet.hsts({
    maxAge: 63072000, // two years
    includeSubDomains: true,
    preload: true,
  })
);

// Stricter frameguard on a sensitive router.
const adminRouter = express.Router();
adminRouter.use(helmet.frameguard({ action: "deny" }));

Only send Strict-Transport-Security over HTTPS. If you set HSTS on a site served over plain HTTP — or set preload before you can guarantee HTTPS on every subdomain forever — you can lock users out of your domain in their browser cache.

This API is identical on Express 4.x and 5.x; helmet’s middleware contract did not change, and the same app.use(helmet()) call works on both.

Best Practices

  • Register helmet() before your routers and static middleware so every response is covered, including error responses.
  • Keep the defaults on and only relax individual headers when a real feature requires it — disabling the whole bundle throws away free protection.
  • Treat the Content-Security-Policy as the primary XSS defense; tune its directives to the exact origins your app loads from rather than using broad wildcards.
  • Roll out a new or tightened CSP in reportOnly mode first, watch the violation reports, then switch to enforcing.
  • Avoid 'unsafe-inline' and 'unsafe-eval' in script-src where possible — they reopen the injection holes CSP exists to close.
  • Only enable HSTS (and especially preload) once HTTPS is guaranteed across the domain and all subdomains.
  • Set helmet behind your TLS terminator and trust the proxy (app.set("trust proxy", 1)) so protocol-sensitive headers behave correctly.
Last updated June 14, 2026
Was this helpful?