What Helmet Sets by Default
This reference is part of the Node.js Express Helmet configuration reference and enumerates the exact response headers a bare app.use(helmet()) produces in Express. Each entry gives the literal header value Helmet emits, the protection it provides, and the option key that turns it off. The values below are Helmet v7 and v8 defaults; they are stable across those major versions unless you override them.
A single helmet() call registers fourteen sub-middlewares. Thirteen add a header; one removes one. Nothing here depends on your routes — Helmet writes these on every response the middleware sees.
Configuration Syntax & Exact Values
The minimal call and the complete set of headers it emits:
const express = require("express");
const helmet = require("helmet");
const app = express();
app.use(helmet());
That produces exactly these response headers on every route registered after it:
Content-Security-Policy: default-src 'self';base-uri 'self';font-src 'self' https: data:;form-action 'self';frame-ancestors 'self';img-src 'self' data:;object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline';upgrade-insecure-requests
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-DNS-Prefetch-Control: off
X-Download-Options: noopen
X-Frame-Options: SAMEORIGIN
X-Permitted-Cross-Domain-Policies: none
X-XSS-Protection: 0
Helmet also removes X-Powered-By, which Express sets to Express by default and which leaks your stack to attackers. There is no header to show for it — its absence is the effect. Note two headers that Helmet does not set by default: Cross-Origin-Embedder-Policy (off, because require-corp breaks pages that load un-CORP cross-origin subresources) and any Permissions-Policy.
The annotated table below is the reference proper. Each row lists the emitted value, the threat it addresses, and the exact key to set to false inside the helmet({ ... }) options object to switch that piece off.
| Header (default value) | Protects against | Disable with |
|---|---|---|
Content-Security-Policy: default-src 'self'; … |
Cross-site scripting, mixed content, clickjacking (via frame-ancestors) |
contentSecurityPolicy: false |
Cross-Origin-Opener-Policy: same-origin |
Cross-window attacks; enables cross-origin isolation | crossOriginOpenerPolicy: false |
Cross-Origin-Resource-Policy: same-origin |
Cross-origin embedding of your resources (Spectre-class reads) | crossOriginResourcePolicy: false |
Origin-Agent-Cluster: ?1 |
Shared memory / same-site origin bleed; requests origin-keyed agent cluster | originAgentCluster: false |
Referrer-Policy: no-referrer |
Leaking full URLs (paths, query) to other origins | referrerPolicy: false |
Strict-Transport-Security: max-age=31536000; includeSubDomains |
Protocol downgrade / SSL-strip on HTTP | strictTransportSecurity: false |
X-Content-Type-Options: nosniff |
MIME sniffing that turns uploads into scripts | xContentTypeOptions: false |
X-DNS-Prefetch-Control: off |
DNS-prefetch privacy leaks | xDnsPrefetchControl: false |
X-Download-Options: noopen |
Legacy IE opening downloads in site context | xDownloadOptions: false |
X-Frame-Options: SAMEORIGIN |
Clickjacking in legacy browsers without CSP | xFrameOptions: false |
X-Permitted-Cross-Domain-Policies: none |
Adobe Flash/PDF cross-domain policy abuse | xPermittedCrossDomainPolicies: false |
X-XSS-Protection: 0 |
Disables buggy legacy XSS auditors (intentionally 0) |
xXssProtection: false |
X-Powered-By removed |
Stack fingerprinting | xPoweredBy: false (keeps the header) |
Two rows carry a trap. X-XSS-Protection: 0 deliberately turns the old browser XSS auditor off — those auditors introduced their own vulnerabilities and are gone from modern browsers, so Helmet neutralises them rather than enabling them. And xPoweredBy: false means “do not run the removal middleware”, which leaves Express’s X-Powered-By: Express in place — the inverse of what the name suggests.
Server-Side Configuration
Helmet is Express middleware; “server-side configuration” here means the JavaScript that mounts and tunes it. Every block below is runnable.
Enable all defaults
Mount Helmet before your routes so its headers apply to their responses. Middleware registered after a route handler that already sent a response never runs.
const express = require("express");
const helmet = require("helmet");
const app = express();
app.use(helmet()); // all defaults, must precede route handlers
app.get("/", (req, res) => res.send("ok"));
app.listen(3000);
Tune the two values you most often change
The default CSP allows 'unsafe-inline' styles and blocks all inline and remote scripts except same-origin, which breaks most real apps. The HSTS max-age of one year is correct for production but must not be sent from localhost or staging on plain HTTP. Override just those, keeping every other default:
app.use(
helmet({
contentSecurityPolicy: {
directives: {
scriptSrc: ["'self'", "https://cdn.example.com"],
},
},
strictTransportSecurity: {
maxAge: 63072000, // 2 years
includeSubDomains: true,
preload: true,
},
})
);
Passing a directives object with useDefaults left at its default of true merges your entries onto Helmet’s baseline CSP — here only scriptSrc is replaced and every other default directive is retained. For a full nonce-based policy, see configuring CSP with Helmet and per-request nonces; for stripping directives, see customizing and disabling Helmet CSP directives.
Disable specific middlewares
Set the option key to false. This disables the Content-Security-Policy so you can add it at a proxy, and turns off Cross-Origin-Opener-Policy and Cross-Origin-Resource-Policy because you serve embeddable widgets:
app.use(
helmet({
contentSecurityPolicy: false,
crossOriginOpenerPolicy: false,
crossOriginResourcePolicy: false,
})
);
Only remove X-Powered-By
If you want none of Helmet’s headers except the fingerprint fix, call the single sub-middleware instead of the bundle:
const helmet = require("helmet");
app.use(helmet.xPoweredBy()); // removes X-Powered-By, adds nothing
Which defaults are safe to ship untouched versus which demand a decision depends on what your app serves. The tree below is the triage.
Diagnostic & Verification Steps
Confirm the full default set with a single curl -I. The -I sends a HEAD request and prints only response headers.
curl -sI http://localhost:3000/
Expected output includes every Helmet header and, crucially, no X-Powered-By line:
HTTP/1.1 200 OK
Content-Security-Policy: default-src 'self';base-uri 'self';font-src 'self' https: data:;form-action 'self';frame-ancestors 'self';img-src 'self' data:;object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline';upgrade-insecure-requests
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-DNS-Prefetch-Control: off
X-Download-Options: noopen
X-Frame-Options: SAMEORIGIN
X-Permitted-Cross-Domain-Policies: none
X-XSS-Protection: 0
Assert that X-Powered-By is gone — the grep must print nothing and exit non-zero:
# Should return no output; a match means the removal middleware did not run
curl -sI http://localhost:3000/ | grep -i '^x-powered-by:'
Count how many of Helmet’s headers are present to catch a partial or duplicated set:
curl -sI http://localhost:3000/ | grep -ciE '^(content-security-policy|cross-origin-opener-policy|cross-origin-resource-policy|origin-agent-cluster|referrer-policy|strict-transport-security|x-content-type-options|x-dns-prefetch-control|x-download-options|x-frame-options|x-permitted-cross-domain-policies|x-xss-protection):'
Expected output: 12. A lower number means Helmet ran after a route already responded, or a middleware was disabled. A higher number means a proxy in front of Express duplicated a header. In Chrome DevTools, open the Network panel, select the document request, and read the Response Headers section; the same twelve names appear there, and the General section will not list X-Powered-By.
The response is assembled in a fixed order: Express would normally stamp X-Powered-By, Helmet’s stack removes it and layers the security headers, then your handler sends the body. The sequence below shows where each mutation happens.
Edge Cases, Security Implications & Safe Rollback
-
X-XSS-Protection: 0is intentional, not a bug. Older browsers shipped an XSS auditor that itself created information-disclosure vulnerabilities, and it is removed from current Chromium and Firefox. Helmet sends0to explicitly disable any lingering auditor. Do not “fix” it to1; mode=block; rely on CSP instead. See the deprecated headers reference for why the auditor was retired. -
HSTS on plain HTTP or localhost poisons clients.
Strict-Transport-Securitywith a one-yearmax-agetells the browser to refuse HTTP for a year. If it is sent from a development box or a staging host reachable overhttp://, that hostname becomes unreachable over HTTP for everyone who visited. Browsers ignore the header on non-HTTPS responses, but proxies and mixed setups can still surface it. Send HSTS only from HTTPS production, and see the HSTS deep dive before addingpreload. -
X-Frame-Options: SAMEORIGINversus CSPframe-ancestors. Helmet sets both the legacy X-Frame-Options header andframe-ancestors 'self'in the default CSP. When both are present modern browsers honourframe-ancestorsand ignoreX-Frame-Options; the legacy header only matters for old clients. If you need to allow a specific external framer, changingX-Frame-Optionsalone does nothing until you also loosenframe-ancestors. -
CORP
same-originblocks legitimate cross-origin embedding.Cross-Origin-Resource-Policy: same-originstops other sites from loading your images, fonts, or scripts as subresources. If you host a public CDN, an embeddable widget, or serve assets a partner site references, that default breaks them. SetcrossOriginResourcePolicy: { policy: "cross-origin" }for those routes rather than disabling it globally.
Safe rollback. Helmet is code-level and non-destructive, but HSTS persists in clients. To back out:
- Remove or narrow the offending option (for example
strictTransportSecurity: false) and redeploy. - If a bad HSTS
max-agereached clients, sendStrict-Transport-Security: max-age=0from HTTPS so browsers expire the pin on their next visit; only then remove the header entirely. - Re-run the
curl -sIchecks above and confirm the header set matches your intent.
Conclusion
Add app.use(helmet()) on staging first and read the twelve headers with curl -sI; tune the CSP for your scripts and set HSTS to a short max-age such as 300 while you verify nothing breaks over HTTPS. Once staging is clean, raise max-age to a year with includeSubDomains and ship to production, leaving the one-line hardening headers at their defaults.
Frequently Asked Questions
Does Helmet set Cross-Origin-Embedder-Policy by default?
No. Cross-Origin-Embedder-Policy is off by default because its require-corp value blocks any cross-origin subresource that does not send a matching CORP or CORS header, which breaks most existing pages. Enable it deliberately with crossOriginEmbedderPolicy: true only when you need full cross-origin isolation for SharedArrayBuffer or precise timers.
Why is X-XSS-Protection set to 0 instead of 1?
The legacy browser XSS auditor introduced its own vulnerabilities and has been removed from Chromium and Firefox. Sending X-XSS-Protection: 0 explicitly disables any remaining auditor so it cannot be abused. Modern XSS defence comes from Content-Security-Policy, not this header.
How do I keep the X-Powered-By header if I want it?
Set xPoweredBy: false in the Helmet options. That disables the removal middleware, so Express’s X-Powered-By: Express stays. Note the inverted meaning: false here means “keep the header”, the opposite of every other option key.
Does the default CSP break my inline scripts and third-party assets?
Yes for scripts. The default script-src 'self' blocks inline <script> and any remote script, though style-src 'self' https: 'unsafe-inline' still permits inline styles. Add your script origins or a per-request nonce to scriptSrc, keeping the other default directives via useDefaults: true.
Is the HSTS max-age of 31536000 safe to ship immediately?
Only from HTTPS production. One year of includeSubDomains means every browser that receives it refuses HTTP to your host and subdomains for a year. Test with a short max-age first, confirm all subdomains serve valid HTTPS, then raise it — and only add preload when you are certain.