Which Node.js Version Should You Run in Production? (November 2026)
Node.js 26 became Active LTS on 28 October 2026, Node 24 moved to Maintenance, and Node 20 is end-of-life. A dated status table, what the new annual release model changes, what Node 26 adds and removes, and an upgrade checklist for an Express + Mongoose app.

Short answer: run Node.js 26 ("Lithium") for anything new, and plan to move existing apps to it. Node 26 became the Active LTS line on 28 October 2026 and is supported until 30 April 2029. Node 24 is still a safe choice: it moved to Maintenance LTS on 20 October 2026 and gets security fixes until 30 April 2028. Node 22 is in Maintenance until 30 April 2027, so treat it as a deadline. Node 20 and Node 25 are end-of-life, and the Node 27 builds you may have seen are alpha releases that are not meant for production.
Below are the details as of 5 November 2026, the change to the release model that starts with Node 27, and the checklist I use to upgrade an Express + Mongoose API.
Node.js release lines on 5 November 2026
These dates come from the official schedule.json in the nodejs/Release repository, and the version numbers from the nodejs.org download index on the same day.
| Line | Codename | Status | Active LTS from | Maintenance from | End of life | Latest release |
|---|---|---|---|---|---|---|
| 27 | (not named yet) | Alpha, not for production | 2027-10-20 (planned) | - | 2030-04-30 | 27.0.0-alpha.0.0.0 |
| 26 | Lithium | Active LTS | 2026-10-28 | 2027-10-20 | 2029-04-30 | 26.13.0 |
| 25 | - | End of life | never LTS | 2026-04-01 | 2026-06-01 | 25.9.0 (final) |
| 24 | Krypton | Maintenance LTS | 2025-10-28 | 2026-10-20 | 2028-04-30 | 24.22.2 |
| 22 | Jod | Maintenance LTS | 2024-10-29 | 2025-10-21 | 2027-04-30 | 22.23.4 |
| 20 | Iron | End of life | 2023-10-24 | 2024-10-22 | 2026-04-30 | 20.20.2 (final) |
A security release for the 26, 24 and 22 lines is pre-announced for "on or shortly after" Wednesday 11 November 2026, with HIGH as the highest severity on every line. If you read this after that date, the "latest release" column will have moved on, so check the download page before you pin anything.
What Current, Active LTS and Maintenance mean
Every major release starts as Current for six months. That phase exists so library authors can catch up, and breaking changes are still landing. Even-numbered lines up to Node 26 then become Active LTS, which is the phase you want for production: bug fixes, security fixes and carefully backported features. After a year the line moves to Maintenance LTS, which means critical bug fixes and security patches only. After that it is end-of-life, and it gets no fixes at all.
The official guidance is a single sentence on the Node.js releases page: "Production applications should only use Active LTS or Maintenance LTS releases." On 5 November 2026 that means 26, 24 or 22, and nothing else.
Two details matter here. First, the Node.js project lists end-of-life versions as affected by every new security release, because nobody checks whether old code paths are vulnerable. If you are still on Node 20, every advisory since April 2026 applies to you. Second, being on a supported line is not enough: you also need the latest patch release of that line. The October 2026 security release, for example, fixed ten CVEs, including CVE-2026-111497, a Brotli decoder out-of-bounds write that the advisory says can lead to a crash, disclosure of process memory or remote code execution. The fixed versions were 26.12.1, 24.22.1 and 22.23.4. Anything older on those lines is exposed.
What changed in the release model
In March 2026 the Node.js project announced that it is moving from two major releases a year to one, starting with Node 27. Node 26 is the last line released under the old model. The new model works like this:
- Alpha (October to March, about six months). Signed and tagged builds for early testing. Semver-major changes are still allowed, and these builds are not intended for production. The first Node 27 alpha appeared at the end of October 2026.
- Current (April to October, six months). Stabilisation. Node 27.0.0 is planned for 22 April 2027.
- LTS (30 months). Every major line now becomes LTS. There is no more odd/even split, so Node 27 will become LTS (planned for 20 October 2027) and reach end-of-life on 30 April 2030.
- Version numbers follow the year of the first Current release: 27 in 2027, 28 in 2028.
For an application team that only runs LTS releases, very little changes. You get one new LTS line every October instead of every other release. For library authors the announcement asks for Alpha builds in CI as early as possible, because testing only against LTS means bugs reach users before you can report them.
One practical consequence: the current gap between LTS lines (22, 24, 26) is two version numbers. From 2027 it will be one (26, 27, 28). If a policy or script assumes "even means LTS", fix it now.
What Node 26 brings
The headline items from the Node 26.0.0 release notes:
- The Temporal API is enabled by default. A modern date and time API that finally gives you time zones, calendar-aware arithmetic and immutable values without a library.
- V8 14.6, which adds
Map.prototype.getOrInsert()andgetOrInsertComputed()(and theWeakMapversions), plusIterator.concat(). - Undici 8, the engine behind the global
fetch(). - npm 11.20.0 bundled with 26.13.0, compared with npm 11.19.1 in Node 24.22.2 and npm 10.9.9 in Node 22.23.4. That matters for security, because the newer npm settings for release-age delays and install-script policies do not exist in npm 10. I cover them in how to protect a Node.js project from npm supply-chain attacks.
Temporal is the one I expect to use first. Here is a small example of what it replaces in a typical API, where you need "the same local time tomorrow" for a reminder:
// Node 26: Temporal is a global, no flag and no library needed.
const now = Temporal.Now.zonedDateTimeISO('Asia/Karachi');
const tomorrowSameTime = now.add({ days: 1 });
console.log(tomorrowSameTime.toString());
// e.g. 2026-11-06T10:30:00+05:00[Asia/Karachi]
// Store an exact instant in MongoDB as a normal Date
const asDate = new Date(tomorrowSameTime.epochMilliseconds);
Mongoose still stores Date values, so the last line is the bridge. If your code must also run on Node 24, keep Temporal behind a feature check or wait until 26 is your minimum version.
What Node 26 removes and deprecates
These are the semver-major changes most likely to affect an Express or Mongoose codebase, again from the 26.0.0 release notes and the deprecations page:
- Removed:
http.Server.prototype.writeHeader(). UsewriteHead(). - Removed: the legacy
_stream_wrap,_stream_readable,_stream_writable,_stream_duplex,_stream_transformand_stream_passthroughmodules. Old packages sometimes import these directly. - Removed: the
--experimental-transform-typesflag. - End-of-life: DEP0182, short GCM authentication tags without an explicit
authTagLength. If you decrypt AES-GCM data, pass the tag length. - Runtime-deprecated (they still work, but warn):
module.register()(DEP0205), passingoptions.typetoDuplex.toWeb()(DEP0201), passing aCryptoKeytonode:cryptoAPIs (DEP0203), andKeyObject.from()with a non-extractableCryptoKey(DEP0204). - Native addons:
NODE_MODULE_VERSIONis now 147, so compiled addons built for Node 24 must be rebuilt or come with a Node 26 prebuilt binary. - Web Storage: reading the global
localStoragewithout the--localstorage-fileflag now returnsundefinedand prints a warning. That is worth knowing if server-side code (an SSR bundle, for example) probes for it.
The LTS promotion release, 26.13.0, contains no functional changes from 26.12.1, but its notes repeat one warning that is easy to miss: since Node 25, the official Linux binaries are linked against libatomic, "which may need to be installed separately depending on your Linux distribution". If you install Node from the official tarballs or with nvm on a minimal VM, check for that library before you switch.
Upgrade checklist for an Express + Mongoose app
The API behind this site runs on Node 24 today: .nvmrc says 24, the Docker image is built from node:24-alpine, and CI reads the version from .nvmrc. This is the checklist I work through for a move like this, in order.
1. Check that your dependencies support Node 26
On 5 November 2026 the current releases declare these engine ranges: Express 5.3.0 needs Node 18 or newer, and Mongoose 9.12.0 and the MongoDB driver 7.8.0 need Node 20.19.0 or newer. All of them allow 26. If the same repository builds an Angular 22 front end, its supported range is ^22.22.3 || ^24.15.0 || ^26.0.0, which also includes 26. You can list what every installed package claims with one command:
# Print every installed package that declares a Node engine range
npm query "*" | node -e "
const pkgs = JSON.parse(require('fs').readFileSync(0, 'utf8'));
for (const p of pkgs) if (p.engines?.node) console.log(p.name + '@' + p.version, p.engines.node);
" | sort -u
Look for upper bounds such as <25 or ^20 || ^22. Those packages either need an update or a replacement.
2. Update the engines field
During the transition I allow both LTS lines, then drop 24 once production has run on 26 for a while:
{
"engines": {
"node": "^24.15.0 || ^26.0.0"
}
}
By default npm only warns about engine mismatches. With engine-strict=true in .npmrc, npm refuses to install packages that declare they do not support the running Node version, which catches problems at install time instead of at runtime.
3. Pin the version in .nvmrc
echo "26" > .nvmrc
nvm install # reads .nvmrc
node -v # v26.x
nvm also understands lts/* and named aliases such as lts/<codename>, but I avoid lts/* in projects. It means "the latest LTS", so it moved from 24 to 26 when Node 26 was promoted on 28 October, and a developer who reinstalled afterwards got a different major version without any commit explaining why. A plain major number changes only when someone changes it on purpose.
4. Change the Docker base image tag
The same applies to Docker. Since the LTS promotion, the node:lts tag points at the same image as node:lithium, which is Node 26. If your Dockerfile says FROM node:lts, you may already be running 26 without having tested it. Use an explicit tag and keep it in a build argument:
# syntax=docker/dockerfile:1.7
ARG NODE_VERSION=26.13.0
FROM node:${NODE_VERSION}-bookworm-slim AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
FROM node:${NODE_VERSION}-bookworm-slim AS runtime
ENV NODE_ENV=production
WORKDIR /app
COPY --from=deps --chown=node:node /app/node_modules ./node_modules
COPY --chown=node:node dist ./dist
USER node
CMD ["node", "dist/server.js"]
Tags such as 26-bookworm-slim, 26-trixie-slim and 26-alpine all exist. A full version such as 26.13.0 gives reproducible builds, but then you must bump it yourself after each security release. Dependabot's Docker updates or Renovate can open that pull request for you. A major-only tag (26-bookworm-slim) picks up patches on every rebuild, but only if you actually rebuild.
5. Run CI on both lines
For a few weeks, test against 24 and 26 so you can compare results and roll back with confidence:
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node: [24, 26]
steps:
- uses: actions/checkout@v7 # pin to a commit SHA, see the supply-chain post
with:
persist-credentials: false
- uses: actions/setup-node@v7
with:
node-version: ${{ matrix.node }}
cache: npm
- run: npm ci
- run: npm test
When 26 becomes the only target, switch back to node-version-file: .nvmrc so the CI version and the developer version come from the same file.
6. Grep for removed and deprecated APIs
# Removed in Node 26
git grep -nE "writeHeader\(|_stream_(wrap|readable|writable|duplex|transform|passthrough)|experimental-transform-types"
# Runtime-deprecated in Node 26 (still work, but warn)
git grep -nE "module\.register\(|Duplex\.toWeb\(|KeyObject\.from\("
# Older deprecations worth cleaning up while you are here
git grep -nE "url\.parse\("
# The same check across dependencies (expect some false positives)
grep -rlE "require\(['\"]_stream_|\.writeHeader\(" node_modules --include=*.js | head
Then let Node tell you what it actually hits at runtime. Running the test suite with deprecation traces shows the stack for every warning, including warnings raised inside dependencies:
NODE_OPTIONS=--trace-deprecation npm test
# Stricter: turn deprecation warnings into thrown errors
NODE_OPTIONS=--throw-deprecation npm test
7. Rebuild native modules
Because the addon ABI changed, delete node_modules and run npm ci on Node 26 rather than reusing a cache built on 24. Packages that ship prebuilt binaries usually just work. Packages that compile on install need a build toolchain in the image. If you do not know which ones you have, find node_modules -name "*.node" lists the compiled addons.
8. Test what matters, then roll out gradually
- Run the full suite, including integration tests against a real MongoDB (I use mongodb-memory-server in CI).
- Start the production image locally and hit the health endpoint. A readiness check that returns 503 when MongoDB is unreachable should still do exactly that. My HTTP status code reference is handy if you are deciding between 500, 502 and 503.
- Exercise authentication end to end. If you sign or verify JWTs, or encrypt anything with
node:crypto, make sure tokens issued under 24 still verify under 26. The JWT decoder helps you compare the header and claims. - Deploy to staging, watch the logs for deprecation warnings and memory use for a few days, and keep the previous image tag ready for a one-line rollback.
When to stay on Node 24 for now
Node 24 is in Maintenance, not end-of-life. It gets security fixes until 30 April 2028, so staying on it for a few more months is a legitimate decision in these cases:
- A native dependency has no Node 26 build yet, and compiling it in your pipeline is not an option.
- Your hosting platform does not offer Node 26 yet. Managed runtimes often add a new LTS line some weeks after it is promoted. Check your provider rather than assuming.
- You are in a change freeze, for example an e-commerce client during a sales season. Upgrade after it, not during it.
Staying on 24 is not a reason to skip patch releases. Keep taking every 24.x security release.
The cases where staying put is not fine: anything on Node 20 or 25 should move now, because those lines get no fixes at all. Anything on Node 22 has until 30 April 2027, and should go straight to 26 rather than stopping at 24. That avoids doing the same work twice.
My recommendation in one list
- New project: Node 26.
- Existing app on 24: move to 26 calmly over the next few months, with a 24/26 CI matrix in between.
- Existing app on 22: plan the jump to 26 now. End-of-life is 30 April 2027.
- Existing app on 20 or older: it is already unsupported, so upgrade first and refactor later.
- Always: run the latest patch release of your line, and subscribe to the Node.js security announcements.
If you would rather hand the upgrade to someone who does this regularly, it is part of my Node.js and Express API development work, and for full Angular + Node projects, my MEAN stack development service.
Sources
- nodejs/Release schedule.json (all phase and end-of-life dates)
- Node.js releases page (release phases and production guidance)
- Evolving the Node.js Release Schedule (annual model from Node 27)
- Node.js 26.0.0 release notes
- Node.js 26.13.0 release notes (LTS promotion, libatomic note)
- Node.js deprecations (DEP0182, DEP0201, DEP0203, DEP0204, DEP0205)
- nodejs/node#61333 (localStorage without
--localstorage-file) - nodejs.org download index (latest versions and bundled npm)
- October 2026 security releases and November 2026 security release announcement
- Angular version compatibility
- Official Node.js Docker images
- nvm: .nvmrc
- npm config: engine-strict
Related posts
Free tools for this topic
- JSON to Mongoose SchemaPaste a JSON document and get a Mongoose schema and model: types, nested sub-schemas, arrays, dates and ObjectId refs. Runs in your browser.
- ObjectId to DateDecode a MongoDB ObjectId to its creation date, or turn a date into an ObjectId for _id range queries. Free and runs in your browser.
- HTTP Status CodesEvery common HTTP status code explained: what 200, 301, 404, 429, 500 and 503 mean, why they happen, Express fixes and the SEO impact.




