Node.js 18 and 20 Are EOL—Yet They Were Downloaded More Than 136 Million Times in July 2026
Node.js 24 is the latest Long-Term Support release, and Node.js 26 is the Current release line. Node.js 18 and Node.js 20, meanwhile, have both reached End-of-Life (EOL).
That should make the direction of travel obvious.
Instead, a HeroDevs analysis of Node.js download statistics counted more than 136 million version-attributed downloads for Node.js 18 and 20 in July 2026.
According to that analysis, Node.js 20 accounted for approximately 103.8 million downloads, while Node.js 18 added another 32.7 million.
Combined, the two unsupported release lines represented roughly one in six version-attributed Node.js downloads during the month.
The calculation is a secondary analysis, not a number published directly as a headline by the Node.js project. Its underlying data comes from Node.js download infrastructure and can be reviewed through the project’s official download metrics.
This is not only a story about old applications that nobody has touched in years.
It is evidence that outdated Node.js versions remain encoded in current installation, build, containerization, and deployment workflows.
In other words, organizations are not merely running legacy runtimes. Many are still repeatedly fetching, provisioning, and redeploying them.
That distinction matters because an EOL runtime does not become safer when it is installed today. It can remain exposed to every applicable vulnerability disclosed after its final upstream security release—and it has no official upstream patch path.
First, a Necessary Data Caveat
More than 136 million downloads does not mean 136 million companies, projects, developers, or production servers.
The official Node.js metrics count anonymized requests for binary and source downloads from Node.js distribution infrastructure.
A download may come from:
- A developer workstation
- A Node.js version manager
- A CI runner
- A container build
- An ephemeral environment
- A cache miss
- A mirror
- An automated provisioning workflow
The data cannot tell us whether an individual event was a first-time installation, a repeated build, or another automated request.
But that limitation does not make the signal irrelevant. It changes the interpretation:
Download telemetry is not a census of production systems. It is a measure of how frequently a release line is still being requested by active tooling and infrastructure.
When EOL versions continue to generate millions of requests every day, the problem is not simply that legacy systems still exist.
The problem is that obsolete version choices remain operationalized.
This pattern predates the July 2026 data. In his 2023 analysis, “You Are Not Updating Node.js,” Node.js Technical Steering Committee member Matteo Collina found that discontinued Node.js 12, 14, and 16 release lines were still generating tens of millions of monthly downloads.
The specific versions have changed, but the operational behavior has not: organizations continue requesting runtimes long after upstream maintenance ends.
The Node.js Support Boundary Has Moved
As of August 2026, the relevant major release lines look like this:
| Release line | Status | Operational meaning |
|---|---|---|
| Node.js 26 | Current | Actively developed and patched; the Current phase gives the ecosystem time to adopt the release |
| Node.js 24 | LTS | The latest LTS line and the primary upgrade target for production applications |
| Node.js 22 | LTS | Still supported and receiving security fixes |
| Node.js 20 | EOL since April 30, 2026 | No official upstream security releases |
| Node.js 18 | EOL since April 30, 2025 | No official upstream security releases |
The official Node.js release schedule recommends that production applications use supported LTS releases.
Once a release line reaches EOL, it no longer receives updates—including security patches—even when later research shows that an applicable vulnerability also exists in the retired branch.
The Node.js project made the scale of this problem explicit in its 2025 article, “Beware of End-of-Life Node.js Versions—Upgrade or Seek Post-EOL Support,” which reported that Node.js 18 alone was still generating approximately 50 million monthly downloads after reaching EOL.
This changes the security equation.
On a supported release line, a vulnerability disclosure produces an actionable sequence:
- The Node.js project publishes an advisory.
- Maintainers release patched binaries.
- Teams update the runtime.
- Security scanners can verify remediation.
On an EOL release line, the sequence stops after disclosure.
The organization receives the vulnerability information, but no official upstream release exists to remediate it.
EOL Does Not Freeze the Threat Model
A common enterprise assumption is that an old runtime is safer because it is stable, familiar, and no longer changing.
That confuses behavioral stability with security maintenance.
A frozen runtime may keep executing the same application code, but the world around it continues to change:
- Security researchers discover previously unknown flaws in long-standing code.
- Attack techniques improve.
- Runtime dependencies such as OpenSSL, V8, nghttp2, llhttp, Undici, and c-ares continue to receive fixes.
- Frameworks and packages drop support for older Node.js majors.
- Operating systems and toolchains move forward.
- Compliance controls become more explicit about unsupported software.
The result is asymmetric risk: the vulnerable code remains frozen, while knowledge of how to exploit it continues to improve.
The official Node.js EOL inventory currently associates 60 vulnerabilities with Node.js 20:
- 27 High
- 24 Medium
- 9 Low
The same inventory associates 38 vulnerabilities with Node.js 18:
- 15 High
- 19 Medium
- 4 Low
That does not mean every application is exploitable through every listed issue.
Exposure still depends on:
- The runtime modules in use
- Reachable code paths
- Application configuration
- Network boundaries
- Deployment architecture
- Compensating security controls
It does mean the retired release lines have no upstream patch train for a growing body of publicly documented risk.
What the 2026 Security Releases Reveal
The security releases published after Node.js 18 and 20 reached EOL are important not simply because of the number of CVEs, but because of where the flaws were found.
They appeared across foundational runtime surfaces.
HTTP/2 Memory and State Management
The official July 2026 Node.js security release addressed two High-severity HTTP/2 vulnerabilities:
- Retained header blocks could bypass
maxSessionMemorycontrols and cause remote memory exhaustion. - Re-entrant HTTP/2 session operations could produce a heap use-after-free condition.
These are not optional third-party packages that can be removed with an npm uninstall.
They sit inside the runtime’s protocol implementation and native memory-management boundary.
For organizations exposing Node.js HTTP/2 servers or using Node.js as an HTTP/2 client, the relevant question is not only whether application code changed.
It is whether the runtime beneath that code can still receive fixes.
TLS, Hostname Verification, and mTLS Identity
The June 2026 and July 2026 security cycles also exposed multiple trust-boundary problems involving:
- Unicode hostname normalization
- Embedded NUL characters in hostnames
- TLS session reuse across different server names
- mTLS identity reuse through HTTPS Agent connection caching
- Case-sensitive SNI context matching
These classes of vulnerability are especially significant in enterprise systems because TLS and mTLS are frequently treated as architectural security controls.
If identity verification is inconsistent inside the runtime, application-level authorization may be operating on top of a false trust decision.
HTTP Parsing and Request Boundaries
Node.js security releases have also addressed request-smuggling and message-boundary issues in HTTP parsing.
These flaws matter because Node.js applications rarely operate alone.
They often sit behind:
- CDNs
- Load balancers
- API gateways
- Reverse proxies
- Service meshes
- Internal routing layers
When two components interpret the same request differently, an attacker may be able to desynchronize the connection and bypass controls enforced by the intermediary.
The issue is not necessarily in the application’s routing logic. It can exist in the boundary between the runtime parser and the surrounding infrastructure.
Permission Controls and Diagnostic APIs
Recent releases found multiple ways for code to bypass intended filesystem or network restrictions in the Node.js Permission Model, including path-prefix errors and privileged diagnostic APIs writing outside configured allowlists.
The Permission Model is not present in every legacy release, so these findings should not be applied indiscriminately to Node.js 18.
That qualification is important.
The broader lesson still holds: security features themselves evolve through adversarial testing. Running the initial implementation forever is not equivalent to running the maintained implementation.
Bundled Dependencies Are Part of the Runtime Risk
A Node.js binary includes and depends on major native and JavaScript components.
Security releases frequently update libraries such as:
- V8
- OpenSSL
- nghttp2
- llhttp
- Undici
- c-ares
This is why upgrading application dependencies alone is insufficient.
A clean npm audit result does not prove that the runtime, its native protocol parsers, its cryptographic implementation, or its bundled dependencies are secure.
npm audit evaluates packages in the dependency graph. It does not replace runtime lifecycle management.
Why Are Teams Still Installing Node.js 18 and 20?
In large organizations, the runtime version is rarely selected in one place.
It may be defined in:
Dockerfilebase images such asnode:18ornode:20.nvmrc,.node-version, or.tool-versions- CI configuration and reusable pipeline templates
actions/setup-nodeinputs- Buildpack or serverless runtime settings
- Infrastructure-as-code modules
- Internal golden images
- Developer environment bootstrap scripts
- Monorepo tooling
- Platform defaults
- Package
enginesconstraints - Native addon compatibility requirements
This creates an important governance problem:
The repository is not the complete unit of upgrade ownership.
A team may update package.json while the production container still uses an EOL base image.
A platform team may update a shared build template while a business unit continues to pin an older image digest.
A developer may use Node.js 24 locally while CI repeatedly provisions Node.js 18.
The outdated runtime keeps returning because it is encoded in automation.
Reproducibility Can Become Fossilization
Pinning versions is a good engineering practice when it creates deterministic builds.
It becomes dangerous when the pin has no maintenance policy.
A Docker image or CI template can reproduce the same environment perfectly for years.
That is operationally convenient, but security depends on reproducing a supported environment—not merely an identical one.
The same mechanisms that make infrastructure reproducible can make obsolete runtime decisions persist indefinitely.
Compatibility Debt Becomes Security Debt
Teams often delay upgrades because of:
- Native addons that must be rebuilt
- Deprecated Node.js APIs
- Abandoned dependencies
- Framework version constraints
- Changes in OpenSSL behavior
- Test suites that do not cover critical runtime behavior
- Unclear service ownership
These are legitimate engineering blockers. They explain why migrations take time.
They do not make EOL safe.
The longer the upgrade is postponed, the more compatibility debt and security exposure accumulate together.
Eventually, the organization is not performing a routine runtime upgrade. It is executing a multi-layer modernization project under incident, compliance, or audit pressure.
The Senior Engineering Response
The correct response is not “update Node.js” as a generic ticket.
It is to treat runtime lifecycle management as a cross-layer engineering capability.
1. Inventory the Runtime That Actually Executes
Do not rely only on documentation or package.json.
Verify the runtime directly:
node --version
node -p "process.versions"
Check each environment independently:
- Local development
- CI
- Staging
- Production containers
- Serverless configuration
- Build images
- Long-running virtual machines
- Kubernetes workloads
- Internal developer platforms
The version developers believe they are using is not always the version executing in production.
2. Find Every Version Decision Point
Search for version pins across:
- Source repositories
- Container definitions
- CI templates
- Infrastructure repositories
- Platform configuration
- Internal starter projects
- Golden images
- Bootstrap scripts
- Deployment manifests
- Buildpacks
An upgrade is incomplete while an upstream template can silently reintroduce the retired version.
The objective is not only to update one application. It is to remove the obsolete release line from the systems capable of creating the next environment.
3. Separate Immediate Patching from Major-Version Migration
For supported versions, deploy the latest patched release in the current major as quickly as possible.
Then plan the major upgrade separately.
This prevents teams from delaying an available security patch because the full modernization effort is larger.
For EOL versions, there is no equivalent upstream patch step.
The available paths are:
- Migrate to a supported Node.js release.
- Use temporary commercial EOL support while the migration proceeds.
- Accept and formally manage the unpatched risk.
The third option should be an explicit business decision, not the accidental result of an abandoned backlog item.
4. Test the Runtime Surfaces That Changed
A senior upgrade plan should explicitly evaluate:
- HTTP/1.1 and HTTP/2 behavior
- TLS, SNI, proxies, and mTLS
- Connection pooling and Agent reuse
- Cryptographic operations
- Native addons and Node-API compatibility
- OpenSSL-dependent integrations
async_hooks, APM, and context propagation- File and network permissions
- Worker threads and child processes
- Build tooling
- Test infrastructure
- Memory and CPU behavior under production-like load
The goal is not only to make the application start.
It is to prove that its production security, performance, and reliability assumptions still hold.
5. Put an Expiration Date on Every Exception
When an application cannot upgrade immediately, the exception should include:
- A named owner
- The exact runtime and patch version
- The affected services
- The reason the upgrade is blocked
- Compensating controls
- A migration plan
- A review date
- A hard expiration date
“Legacy” should describe a managed transition state, not permanent ownership ambiguity.
6. Prevent the Old Version from Returning
Once the application is upgraded, enforce the decision.
Examples include:
- CI policies that reject EOL Node.js versions
- Container scanning rules
- Approved base-image catalogs
- Renovate or Dependabot rules for runtime references
- Centralized runtime templates
- Platform-level deprecation notices
- Runtime inventory dashboards
- Deployment checks against unsupported versions
An upgrade is not durable until the organization prevents future builds from silently returning to the old release.
Start with Evidence, Not Guesswork
The hardest part of a major Node.js upgrade is often not changing the runtime.
It is discovering what will break:
- Which dependencies block the target version?
- Which native addons require recompilation?
- Which deprecated APIs are still used?
- Which security findings exist in the current stack?
- Which changes require code modifications rather than configuration updates?
The NodeSource Node.js LTS Upgrade & Modernization program, developed in collaboration with the OpenJS Foundation, starts with a local discovery scan:
npx @nodesource/upgrade
The scan identifies:
- Application dependencies
- Native C++ addons
- Deprecated API usage
- Vulnerable packages
- Potential migration blockers
The application source code remains in the local environment. The generated discovery report can then be used to evaluate security risk, upgrade complexity, native addon conflicts, and the work required to reach a supported release.
Teams can use that analysis to turn an uncertain upgrade into a phased engineering plan.
The Real Warning in the 136 Million Downloads
The most concerning part of the July numbers is not that Node.js 18 and 20 still exist.
Long-lived enterprise systems are expected to outlast technology release cycles.
The warning is that EOL runtimes continue to appear in active download and provisioning traffic at enormous scale.
More than 136 million downloads in one month suggest that version retirement has not propagated through the templates, images, pipelines, and platforms that create new environments.
A Node.js 18 binary downloaded today does not deliver the security posture Node.js 18 had when it was an actively supported release.
It delivers a runtime whose final upstream patch was issued in 2025, operating in a threat landscape shaped by disclosures from 2026.
That is not stability.
It is unpatched software being reproduced reliably.
Sources and Further Reading
Primary Sources
- Node.js Download Metrics — anonymized Node.js binary and source download records.
- Node.js Release Schedule — official lifecycle and support status for each release line.
- Node.js End-of-Life Inventory — official EOL guidance and vulnerability counts by release line.
- Node.js: Beware of End-of-Life Versions — official EOL guidance and historical download context.
- Node.js July 2026 Security Releases — upstream advisory and patched release lines.
- Node.js June 2026 Security Releases — upstream advisory covering TLS, HTTP/2, and other runtime issues.
Industry Analysis
- HeroDevs: Node.js July 2026 Security Release—11 CVEs, Node 18 and 20 EOL — secondary analysis of Node.js download data that calculated more than 136 million July downloads for Node.js 18 and 20.
- Matteo Collina: You Are Not Updating Node.js — earlier analysis of Node.js download data and slow migration away from unsupported versions.