Node.js 20 reaches end of life on April 30, 2026.
If you are reading this in March or April, you are already behind. Node.js EOL dates do not come with a grace period. On May 1st, no more security patches. No more CVE fixes. The npm ecosystem moves on, and packages start dropping support in their CI matrices before the EOL date even arrives.
I have seen teams discover they are running an EOL runtime at the worst possible moment — during a security incident, when the fix only ships for supported versions. This playbook covers what Node 20 EOL means, which version to move to, what breaks along the way, and the exact steps to migrate without a production outage.
TL;DR — What to do and when
- Right now (February): Audit every service, container, Lambda function, and CI pipeline running Node 20. Run your test suite on Node 22.
- March: Migrate production workloads to Node 22 LTS (recommended) or Node 24 if you need the latest features. Deploy behind canary or feature flags.
- Early April: Clean up stragglers — serverless functions, internal tools, batch jobs, developer machines.
- April 30: Deadline. You want to be done a week before, not on the day.
If you do one thing today, run node --version across every production host, container, and CI runner. The number of places pinning Node 20 will surprise you.
What “end of life” actually means for Node 20
Node.js 20 entered Maintenance LTS on October 22, 2024. Since then, it only receives critical bug fixes and security patches. On April 30, 2026, even that stops.
- No more security patches: If a vulnerability is found in Node 20 after April 30, the Node.js team will only patch supported versions (22, 24+). You get nothing.
- npm ecosystem moves on: Package authors drop Node 20 from their
enginesfield and CI matrices. Some already have. When a package you depend on releases a version that requires Node 22+, your lockfile becomes a ticking time bomb. - Cloud runtimes deprecate: AWS Lambda, Google Cloud Functions, and Azure Functions will deprecate their Node 20 runtimes on their own timeline. AWS gives at least 180 days notice and phases out in stages: first blocking new function creation, then updates, though existing invocations can continue indefinitely on deprecated runtimes. Other providers have similar but not identical policies.
- Compliance gaps: SOC 2, PCI DSS, and ISO 27001 all require running supported software. An EOL runtime is a finding waiting to happen.
Your code still runs. Node.js does not brick itself. But security coverage evaporates and the maintenance burden increases every week as the ecosystem leaves you behind.
Where Node 20 is hiding
🔔 Never Miss a Breaking Change
Monthly release roundup — breaking changes, security patches, and upgrade guides across your stack.
✅ You're in! Check your inbox for confirmation.
The obvious places are easy. The ones that bite you are the ones nobody remembers deploying.
- Docker base images:
node:20,node:20-slim,node:20-alpine. Search your Dockerfiles:grep -rn "FROM node:20" . --include="Dockerfile*". Check multi-stage builds too. - .nvmrc and .node-version files: These pin the Node version for local development and often get copied into CI. Search:
find . -name ".nvmrc" -o -name ".node-version" | xargs grep "20" - package.json engines field:
grep -rn '"engines"' . --include="package.json" -A 3 | grep "20" - CI/CD pipelines: GitHub Actions (
setup-node), GitLab CI, CircleCI, and Jenkins configs. Search fornode-version: '20'orNODE_VERSION: 20across all YAML files. - AWS Lambda: Check runtime settings:
aws lambda list-functions --query 'Functions[?Runtime==\`nodejs20.x\`].[FunctionName,Runtime]' --output table - Vercel / Netlify / Cloudflare Workers: Check project settings for Node version overrides. Vercel uses
engines.nodein package.json. Netlify uses environment variables. Cloudflare Workers has its own compatibility dates. - Tooling: Husky, lint-staged, Prettier, ESLint config runners — these run on your dev machine’s Node version, which developers may not have updated.
Which version to upgrade to
Short answer: Node 22 LTS for production. Node 24 if you are already running it in development and can tolerate a shorter track record.
Node 22 LTS (recommended for most teams)
- Entered Active LTS in October 2024, Maintenance LTS started October 21, 2025
- EOL April 30, 2027 — gives you a full year of support after Node 20 dies
- V8 engine 12.4 — significant performance improvements over Node 20’s V8 11.3
- Key additions over Node 20:
require()now works with ES modules (release candidate status as of 22.x — usable in production but check your specific minor version) — the biggest quality-of-life improvement in years- Built-in
node --watchis stable (no more nodemon for simple use cases) fetch()andWebStreamsare stable (no longer experimental)- Built-in WebSocket client (
WebSocketglobal) — stable (was experimental behind a flag in Node 20.10+) - Improved test runner (
node:test) with snapshot testing and coverage — the test runner is stable in Node 20, but Node 22 adds significant features globandmatchesGlobinnode:fsandnode:path- Task runner:
node --run <script>as a faster alternative tonpm run
- Breaking changes from Node 20: V8 upgrade may affect native addons compiled against Node 20’s ABI. Rebuild native modules with
npm rebuildornode-gyp rebuild.
Node 24 (for early adopters)
- Released May 6, 2025, entered Active LTS October 2025
- EOL April 2028 — nearly two years of runway
- V8 13.x with further performance improvements
- Permissions model stable, TypeScript type stripping is now stable (the old
--experimental-strip-typesflag was removed in 24.12+) - Risk: Some packages may not have been fully tested against Node 24’s V8 engine. Native addons are the usual pain point.
My recommendation for February 2026: jump to Node 22 LTS. It is battle-tested, has the widest ecosystem compatibility, and gives you a year before you need to think about versions again. If you are starting a new project, consider Node 24 from day one.
What breaks when you upgrade from Node 20 to 22
The gap is one LTS version (20 → 22), which is the smallest possible LTS jump. Good news: this is usually straightforward. Bad news: “usually” is not “always.”
V8 engine changes
Node 22 ships V8 12.4 (Node 20 had V8 11.3). This matters if you:
- Use native addons compiled against Node 20. Fix:
npm rebuildafter upgrading. Most addons recompile automatically, but some with pinned prebuilt binaries (sharp, bcrypt, better-sqlite3) may need an explicit version bump. - Rely on specific V8 flags for performance tuning. Some flags change between V8 versions. Check your
--v8-*flags still exist.
Deprecated APIs removed or changed
punycodemodule: Runtime deprecation warning in Node 20, still importable. In Node 22, the warning is louder and the module is scheduled for removal. Use thepunycode/npm package instead.SlowBuffer: If you somehow still use this, switch toBuffer.allocUnsafe().url.parse(): Still works but URL constructor is preferred. Some edge cases around auth parsing were tightened in Node 22.- OpenSSL 3.x changes: Node 22 may use a newer OpenSSL patch that affects TLS behavior. If you connect to systems with legacy TLS configurations, test your HTTPS connections thoroughly.
ESM/CJS interop changes
This is the area most likely to cause confusion, not breakage:
- Node 22 supports
require()for ES modules. This is unflagged but at “release candidate” stability (1.2) — usable and increasingly relied on, but not yet fully stable. It does not break existing CJS code. - If you have a mixed ESM/CJS codebase, test both
importandrequirepaths after upgrading. - The
"type": "module"field in package.json behaves the same way. No changes there.
Step-by-step migration
1. Audit your Node.js footprint
# Check all hosts and containers
node --version
# Find pinned versions in your codebase
grep -rn "FROM node:20" . --include="Dockerfile*"
find . -name ".nvmrc" -exec cat {} \; -print
find . -name ".node-version" -exec cat {} \; -print
grep -rn '"node"' . --include="package.json" | grep "20"
# Check AWS Lambda functions
aws lambda list-functions \
--query 'Functions[?Runtime==`nodejs20.x`].[FunctionName]' \
--output table
2. Test on Node 22 locally
# Using nvm
nvm install 22
nvm use 22
npm ci
npm test
# Or with Docker
# Before
FROM node:20-slim
# After
FROM node:22-slim
Watch for:
- Native addon compilation failures (most common: sharp, bcrypt, better-sqlite3, canvas)
- Test failures from tightened URL parsing or crypto behavior
- Deprecation warnings that became errors
3. Fix dependency issues
# Rebuild all native addons
npm rebuild
# Check for packages that declare Node engine requirements
npx check-engines
# Update packages that need newer versions for Node 22
npm outdated
npm update
Common packages that needed updates for Node 22:
- sharp: Needs 0.33+ for Node 22 prebuilt binaries
- bcrypt: Needs 5.1+ for Node 22 ABI compatibility
- node-sass: Dead project. Switch to
sass(Dart Sass) immediately — this will not get Node 22 support. - better-sqlite3: Needs 11+ for Node 22
- Prisma: 5.x supports Node 22. If you are on Prisma 4.x, this is a good time to upgrade.
4. Update CI pipelines
# GitHub Actions — run both during migration
- uses: actions/setup-node@v6
with:
node-version: '22' # was '20'
# To test both versions in a matrix:
strategy:
matrix:
node-version: ['20', '22']
5. Update Docker base images
# Pin the specific LTS version
FROM node:22-slim
# If you were on Alpine
FROM node:22-alpine
Important: If you use multi-stage builds, update ALL stages — not just the final one. A common mistake is updating the runtime stage but leaving the builder stage on Node 20.
6. Update serverless runtimes
# AWS Lambda — update runtime in SAM/CloudFormation
Runtime: nodejs22.x # was nodejs20.x
# Or via AWS CLI
aws lambda update-function-configuration \
--function-name my-function \
--runtime nodejs22.x
# Vercel — update package.json
"engines": {
"node": "22.x"
}
# Netlify — update environment variable
NODE_VERSION=22
7. Deploy with a canary
Do not upgrade every service simultaneously. Pick your least-critical production service. Deploy with Node 22. Watch error rates, latency, and memory usage for 48 hours. Then roll forward to the next service.
Pay particular attention to:
- Memory usage (V8 12.4 may have different heap behavior)
- Cold start times in serverless (first request after deploy)
- TLS handshake failures if connecting to legacy systems
Node 20 vs 22 vs 24: Quick comparison
| Node 20 LTS | Node 22 LTS | Node 24 | |
|---|---|---|---|
| Release date | April 2023 | April 2024 | May 2025 |
| Active LTS start | October 2023 | October 2024 | October 2025 |
| EOL | April 30, 2026 | April 30, 2027 | April 30, 2028 |
| V8 engine | 11.3 | 12.4 | 13.x |
| fetch() | Stable (since 21.x backport) | Stable | Stable |
| WebSocket | Experimental (20.10+, flag) | Stable | Stable |
| require(esm) | No | Release candidate | Stable |
| Test runner | Stable | Stable (enhanced) | Stable (enhanced) |
| Watch mode | Stable (since 20.13) | Stable | Stable |
| Ecosystem support | Universal | Universal | Most packages |
The one thing nobody tells you about Node version migrations
The breakage is almost never in your application code. It is in native addons. Specifically, it is the one C++ addon that was compiled against Node 20’s ABI and ships a prebuilt binary that does not exist for Node 22 yet.
When this happens, npm ci either fails with a compilation error (if you do not have build tools installed) or silently downloads a binary for the wrong ABI (which then crashes at runtime with NODE_MODULE_VERSION mismatch).
The fix: always run npm rebuild after switching Node versions. Add it to your Dockerfile. Add it to your CI setup step. Make it automatic so you never think about it again.
Related Reading
- Node 20 vs 22 vs 24: Which LTS Should You Run in Production? — detailed comparison of all three versions
- Node.js 20.20.0 Security Patches — the final security releases you should be running
- Node.js 22.22.0 LTS Security Release — what to expect after migrating
- Node.js 24.13.1 Release Notes — if you’re considering the current release line
- undici v7.18.2 Critical Security Patch — the HTTP client vulnerability that affects all Node versions
- Node.js 22.22.0 TLSSocket Changes — a subtle behaviour change to watch for during migration
Frequently Asked Questions
When exactly does Node 20 reach end of life? April 30, 2026. After this date, the Node.js project will not release any further patches for the 20.x line. If a CVE is found in Node 20 after this date, the fix will only ship for Node 22+ and you will need to upgrade to receive it.
Can I skip Node 22 and go straight to Node 24? Yes, if Node 24 has entered Active LTS by the time you migrate (October 2025). The jump from Node 20 to 24 is larger — two V8 major versions — so expect more native addon rebuilds and test more thoroughly. For most teams, Node 22 is the safer choice because it has been in production for over a year.
Does Node 20 EOL affect my operating system? Linux distributions that ship Node 20 as a system package (some Debian/Ubuntu versions) may continue to backport security patches on their own timeline. However, this only covers the Node binary itself — not npm packages, not your application dependencies. For application security, you need an upstream-supported Node version.
Will AWS Lambda stop running Node 20 functions on April 30? No. AWS provides at least 180 days notice before deprecating a runtime, then phases out in stages: first blocking new function creation, then blocking updates. Existing invocations can continue indefinitely on deprecated runtimes — AWS does not forcibly stop running functions. But running an EOL runtime in Lambda means neither upstream Node.js nor AWS is patching it, so your functions are exposed to any vulnerabilities found after the EOL date. Do not confuse “still runs” with “still safe.”
How do I check which Node version my production containers are running? If you use Docker, check your base image tags. For running containers: docker exec <container> node --version. For Kubernetes: kubectl exec <pod> -- node --version. For a fleet, consider adding Node version to your health check endpoint so monitoring can track it.
What about TypeScript? TypeScript itself runs on whatever Node version you have — the compiler is pure JavaScript. The concern is with @types/node: make sure you update to @types/node@22 to get accurate type definitions for Node 22’s APIs. Also check that your tsconfig.json target and lib settings are appropriate for Node 22’s V8 version.
🛠️ Try These Free Tools
Paste your Kubernetes YAML to detect deprecated APIs before upgrading.
Paste a Dockerfile for instant security and best-practice analysis.
Paste your dependency file to check for end-of-life packages.
Stay ahead of breaking changes
Free email alerts for EOL dates, CVEs, and major releases across your stack.