Upgrade to Node.js 20
Node.js 20 is the active LTS and the recommended target for anything still on Node 16 (EOL) or Node 18 (maintenance). The risk is concentrated in native-module rebuilds, OpenSSL 3 behavior, and removed deprecated APIs — not in rewriting application code.
Last verified · Updated May 22, 2026
Node 20 is the active LTS and a recommended upgrade for apps on Node 16 (EOL) or Node 18 (maintenance). Most breakage comes from native-module ABI rebuilds, OpenSSL 3 stricter defaults, and removed deprecated APIs — not from rewriting code. Budget half a day to three days depending on how many native dependencies you ship.
Who should upgrade
- Anyone still on Node 16 — it is end-of-life and receives no security patches.
- Teams on Node 18 wanting the stable node:test runner, a newer V8, and the longest remaining LTS window.
- Services that want built-in fetch and to drop a node-fetch/axios polyfill dependency.
Who should wait
- Apps blocked by a critical native dependency without a Node 20-compatible prebuilt binary.
- Builds pinned to an OpenSSL 1.1 behavior that breaks under OpenSSL 3's stricter defaults until the code is fixed.
What changed
- Stable built-in test runner via the node:test module — no external test framework required for basic suites.
- Global fetch, Request, Response, and Headers are available without a polyfill.
- Several previously --experimental flags graduated toward stable.
- Experimental Permission Model (--experimental-permission) to restrict file system, child process, and worker access.
- Newer V8 engine with updated language and performance features.
- Ships with an updated bundled npm and is built against OpenSSL 3.
A new Node major bumps the V8/N-API ABI, so any package with native bindings (binding.gyp, prebuilt .node files) must be reinstalled or rebuilt with npm rebuild. Many 'mysterious' crashes right after an upgrade are stale native addons, not application bugs.
Recommended upgrade path
# 1. Pin the new version locally$ nvm install 20 && nvm use 20$ node --version# 2. Reinstall and rebuild native modules against the new ABI$ rm -rf node_modules && npm ci$ npm rebuild# 3. Run the test suite on Node 20$ npm testLTS support matrix
| Version | Status | LTS | Released | End of life |
|---|---|---|---|---|
| Node 16.x | End-of-life | Yes | 2021-04-20 | 2023-09-11 |
| Node 18.x | Maintenance | Yes | 2022-04-19 | 2025-04-30 |
| Node 20.x | Active LTS | Yes | 2023-04-18 | Active — target this |
Upgrade this project to Node.js 20 LTS. First inspect engines.node, .nvmrc, the CI node-version matrix, the Docker base image, and any native dependencies. Then bump the engine and pin .nvmrc, update CI and Docker, run `npm ci` followed by `npm rebuild`, and run the test suite. Triage failures: rebuild native modules first, then check for removed deprecated APIs and OpenSSL 3 fallout. Run install and tests after each step and report before continuing.
Safety: Incremental edits only. Pause for review after the engine/CI changes and after the native rebuild. Do not silently upgrade unrelated dependencies.
PR review checklist
- engines.node, .nvmrc, the CI matrix, and the Docker base image all reference Node 20
- node_modules was reinstalled and native modules rebuilt against the new ABI
- No removed/deprecated Node APIs remain in changed code paths
- Any node-fetch/axios used only as a fetch polyfill was reviewed against built-in fetch
- No new Node deprecation warnings appear in the test output
Rollback strategy
- Keep the engine bump, CI/Docker changes, and native rebuild in separate commits.
- Revert .nvmrc and the Docker base image to the previous LTS and reinstall if a blocking native incompatibility appears.
- Hold the upgrade behind a release branch until the full test suite is green on Node 20 in CI.
Common errors
- 'Module did not self-register' or 'NODE_MODULE_VERSION' mismatch — rebuild native modules with npm rebuild.
- TLS/crypto errors after OpenSSL 3 — see the fix-node-dependency-errors workflow.
- 'engine "node" is incompatible' from a dependency — find a Node 20-compatible release or replacement.
Related migration paths
Official sources
Backs the breaking-change and migration-step claims.
Backs the breaking-change and migration-step claims.
Backs the breaking-change and migration-step claims.
Copy-ready AI prompts
Structured prompts for an AI coding assistant. Inspect first, then execute incrementally, and keep a human in the review loop.
You are helping with a Node.js migration: upgrade to Node.js 20 LTS. Do not edit files yet. First inspect the repository and report: 1. The current Node version targets: the "engines.node" field in package.json, the .nvmrc / .node-version file, and the node-version used in CI (.github/workflows/*.yml) and any Dockerfile base image. 2. The module system: whether package.json sets "type": "module", and the mix of .js / .cjs / .mjs files. 3. Native modules (packages with binding.gyp or install scripts) that must be rebuilt against the new ABI. 4. Usage of deprecated or removed APIs surfaced by the new major's deprecations (e.g. legacy url.parse patterns, the old assert API, removed crypto/buffer constructors). 5. The install, build, and test commands and whether the lockfile is committed. Return: a risk summary, the highest-risk files and dependencies, a suggested migration order, the commands to run before editing, and any questions that need human confirmation.
Safety: Inspection only. The agent must not modify files in this step.
Works with Claude Code, Cursor, GitHub Copilot
Perform the migration (upgrade to Node.js 20 LTS) one concern at a time. Work in this order and pause for review after each: (1) bump "engines.node" in package.json and pin the new version in .nvmrc, (2) update every CI matrix entry and Docker base image to the new Node version, (3) reinstall and rebuild native modules with `npm rebuild`, (4) run the test suite and triage runtime failures, (5) replace any deprecated/removed APIs surfaced during inspection. After each step run the project's install and test commands and report results before continuing. Do not bundle unrelated refactors or upgrade dependencies that are not required by the Node bump.
Safety: Apply changes incrementally and keep each step reviewable. Rebuild native modules before assuming a failure is a code problem.
Works with Claude Code, Cursor, GitHub Copilot
Test plan
Commands
node --versionnpm cinpm rebuildnpm testnpm run build
Manual checks
- Native modules: confirm packages with native bindings load without ABI/version errors at runtime.
- Startup: boot the app on the new Node version and watch for new deprecation warnings.
- Globals: verify code relying on built-in fetch, the test runner, or other newly-stable APIs behaves the same locally and in CI.
Regression risks
- Native addons compiled against the old ABI failing to load until rebuilt.
- Deprecated APIs removed in the new major breaking code paths not covered by tests.
- Dependencies that declare an engines range excluding the new Node version.
Acceptance criteria
- Install, build, and the full test suite pass on the target Node version.
- CI, .nvmrc, and the Docker image all reference the same Node version.
- No new Node deprecation warnings appear in the test output.
Frequently asked questions
Is upgrading to Node 20 a hard upgrade?
For most services it is straightforward — the bulk of the work is updating the engine pin, CI matrix, and Docker image, then reinstalling and rebuilding native modules. Effort scales with how many native dependencies you ship and whether any code depends on OpenSSL 1.1 behavior or APIs removed in newer majors.
Do I have to remove node-fetch now that fetch is built in?
No. Built-in fetch is available globally on Node 18+ and stable on Node 20, but keeping a node-fetch/axios dependency still works. Removing the polyfill is an optional cleanup you can do incrementally once you have confirmed the built-in matches your usage.