Guide
Still on Node.js 20? This week's security fixes skip you — a safe move to 22 or 24
By Aman Singh · Published · 9 min read
On 21 October 2026 Node.js released fixes for ten vulnerabilities, including Node2Shell (CVE-2026-111497), a critical remote-code-execution bug in the bundled Brotli decoder. The fixes shipped for the 22, 24 and 26 lines — 26.12.1, 24.22.1 and 22.23.4. They did not ship for Node.js 20, which is end of life and receives no security fixes at all.
That is the trap: on Node.js 20 there is no patch version to move to. "Bump the Node version" does nothing, because the fix only exists on newer lines. A Node 20 app stays exposed to Node2Shell — and to this month's TLS and DNS fixes — until you move it to a supported line. Vercel says the same in its own advisory: "Node.js 20 reached end of life and will not receive this fix." This guide is the safe way to make that move.
Want the upgrade done and the breakage fixed for you? Security patching →
Why this week's fix skips Node 20
Node.js security fixes are backported only to lines that are still in support. Node.js 20 ("Iron") reached end of life in 2026, so it dropped out of that list. The October release carried the fix to 22, 24 and 26; there is no 20.x build that contains it, and there will not be one.
Node2Shell is the headline, but the same releases also fixed TLS hostname-check bypasses and a DNS truncation bug, and refreshed the root certificates (blocking the compromised eMudhra intermediates). Every one of those skips Node 20 as well. Staying on 20 is not "one unpatched CVE"; it is a runtime that no longer gets any.
Which line to move to
| Target | Why | Choose it when |
|---|---|---|
22.23.4 | LTS, closest to Node 20 — the smallest behaviour change | You want the least risk and the fastest move |
24.22.1 | LTS, supported into 2028 | Most apps: the best balance of support runway and stability |
26.12.1 | Newest LTS line (active from 28 October 2026) | You want the longest runway and can absorb a bigger jump |
For most teams I recommend 24.22.1: it is a current LTS with support into 2028, and the jump from 20 is well-trodden. Pick 22.23.4 if you need the smallest possible change today and will move again later.
A safe move, in order
- 1Find every place Node 20 runs. Your laptop, CI matrix, the Dockerfile
FROM, the Vercel project setting, the Lambda runtime, and any PM2 or systemd host. Each is a separate change. - 2Install the target locally and reinstall from scratch. Delete
node_modulesand the lock file so native add-ons (such asbetter-sqlite3,bcrypt,sharp) rebuild for the new ABI, then run your full test suite.Terminal # install a supported line locally (nvm shown; fnm is the same idea) nvm install 24.22.1 && nvm use 24.22.1 # fresh install so native modules rebuild against the new ABI rm -rf node_modules package-lock.json npm install npm test - 3Update the version in every environment and redeploy. Bump
engines.node, the Dockerfile, the CI matrix and the platform setting together, so nothing silently falls back to 20.By environment # package.json: state the floor you now support { "engines": { "node": ">=22.23.4" } } # Dockerfile: pin a patched line, then rebuild and redeploy FROM node:24.22.1-alpine # Vercel: Project Settings -> Node.js Version -> 24.x, then redeploy # AWS Lambda: set the runtime to nodejs24.x (or nodejs22.x) # PM2 / systemd host: install the new Node, then restart the service pm2 restart all # or: sudo systemctl restart my-api - 4Restart long-running processes. A running PM2 or systemd process keeps the old binary until it restarts; pulling a new image does not change a container that is already running.
- 5Verify on the deployed machine, not only locally (see below).
What usually breaks (and what doesn't)
Going from 20 to 22 or 24 is a routine upgrade for most applications, but a few things are worth checking before you deploy:
- Native modules. Anything compiled (node-gyp based) needs a version with prebuilt binaries for the new line, or a working build toolchain. Most breakage lives here; the fresh reinstall above surfaces it.
- Deprecated APIs. Long-deprecated corners (the legacy
url.parsebehaviour, thepunycodebuilt-in, the oldBuffer()constructor) warn or are gone. Grep your code and dependencies for them. - Pinned Node in tooling.
.nvmrc, Volta pins, CI images and any Docker base you forgot — these keep an app on 20 after you "upgraded". - Certificate checks are stricter. This month's TLS fixes mean connections to hosts written as non-canonical IP addresses can now fail certificate validation. Test any internal service you reach by raw IP.
- What rarely breaks: standard HTTP servers, Express/Fastify apps, and pure-JS dependencies. OpenSSL 3 was already in Node 20, so that jump is behind you.
Verify the fix landed
Confirm the running runtime is both on a new line and actually patched. The surest test is a zlib constant that only the patched Brotli decoder exposes — it is present on every fixed build and absent on every Node 20 build.
# on the deployed machine or container, not just your laptop
node -v
node -p "require('zlib').constants.BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH"
# patched builds print -17; unpatched (incl. all of Node 20) print undefinedCommon questions
Can I just install the latest Node 20 patch to fix Node2Shell?+
Should I go to 22, 24 or 26?+
I'm on Vercel or AWS Lambda — do they handle this?+
How do I know the upgrade actually took effect in production?+
Want the upgrade done and the breakage fixed for you?
I move Node.js apps off end-of-life lines, rebuild native modules, fix what the jump breaks, and verify every environment is on a patched runtime. See security patching or Node.js API development. Related: the Node2Shell check-and-patch guide.
Sources
Next steps
Keep reading
Need a hand with this?
Tell me what you are working on. Free scoping call, fixed quote within 48 hours.
Book a call