ERR_DLOPEN_FAILED when a native addon (.node binary) was compiled for a different Node.js ABI (NODE_MODULE_VERSION) than the one currently running — most commonly after a Node.js upgrade. Fix: run npm rebuild to recompile all native addons. If that fails, delete node_modules and run npm install. For Electron, use electron-rebuild instead. On Apple Silicon, ensure Node.js and npm share the same CPU architecture (arm64 vs x64).
What is ERR_DLOPEN_FAILED?
ERR_DLOPEN_FAILED is thrown when Node.js calls the operating system's
dlopen() function to load a native addon (.node file) and that
call fails. Native addons are compiled C/C++ binaries that link directly against Node.js
internals. They are built for a specific Node.js ABI version, identified by the
integer NODE_MODULE_VERSION. When the runtime Node.js version does not
match the compiled version, the OS refuses to load the binary and Node.js surfaces the
failure as ERR_DLOPEN_FAILED.
The official Node.js documentation lists this under errors — ERR_DLOPEN_FAILED. The underlying cause is almost always an ABI mismatch, a CPU architecture mismatch, or a missing shared library dependency.
Full Error Examples
Error: The module '/project/node_modules/bcrypt/lib/binding/napi-v3/bcrypt_lib.node'
was compiled against a different Node.js version using
NODE_MODULE_VERSION 93. This version of Node.js requires
NODE_MODULE_VERSION 115. Please try re-compiling or re-installing
the module (for instance, using `npm rebuild` or `npm install`).
at Object.Module._extensions..node (node:internal/modules/cjs/loader:1329:18)
at Module.load (node:internal/modules/cjs/loader:1105:32)
at Function.Module._load (node:internal/modules/cjs/loader:946:12)
at Module.require (node:internal/modules/cjs/loader:1127:19) {
code: 'ERR_DLOPEN_FAILED'
}
Error: dlopen(/project/node_modules/sharp/build/Release/sharp-darwin-arm64v8.node, 0x0001):
tried: '/project/node_modules/sharp/build/Release/sharp-darwin-arm64v8.node'
(mach-o file, but is an incompatible architecture (have 'x86_64', need 'arm64e'))
at Object.Module._extensions..node (node:internal/modules/cjs/loader:1329:18) {
code: 'ERR_DLOPEN_FAILED'
}
Error: /project/node_modules/canvas/build/Release/canvas.node: invalid ELF header
at Object.Module._extensions..node (node:internal/modules/cjs/loader:1329:18) {
code: 'ERR_DLOPEN_FAILED'
}
was compiled against a different Node.js version using NODE_MODULE_VERSIONError: dlopen failed, was compiled for a different Node.js versionmach-o file, but is an incompatible architecture (have 'x86_64', need 'arm64e')invalid ELF headercannot open shared object file
NODE_MODULE_VERSION — The ABI Version Table
Every Node.js major version assigns a unique NODE_MODULE_VERSION integer.
Native addons embed this number at compile time. If the runtime Node.js has a different
number, loading fails. The table below covers all active and recent LTS releases:
| Node.js version | NODE_MODULE_VERSION | Release status (2026) |
|---|---|---|
| Node.js 14.x | 83 | End-of-life |
| Node.js 16.x | 93 | End-of-life |
| Node.js 18.x | 108 | Maintenance LTS |
| Node.js 20.x | 115 | Active LTS |
| Node.js 22.x | 127 | Current |
| Node.js 23.x | 131 | Current |
The mismatch numbers in the error message tell you exactly what went wrong. For example, "compiled against NODE_MODULE_VERSION 93, requires 115" means the addon was built with Node 16 but is running under Node 20.
All Causes at a Glance
| Cause | Typical scenario | Fix |
|---|---|---|
| Node.js version upgraded without rebuilding | nvm use 20 after installing with Node 18; CI runner updated base image |
npm rebuild |
| node_modules copied from another machine or Docker layer | Checked-in node_modules, Docker multi-stage copy without rebuild |
Delete node_modules, run npm install on target machine |
| Apple Silicon CPU architecture mismatch | x86_64 .node binary on arm64 Node.js, or vice versa |
Reinstall Node.js for correct arch; npm rebuild |
| Electron's bundled Node.js vs system Node.js | npm rebuild targets system Node.js, not Electron's internal version | Use electron-rebuild |
| Prebuilt binary for wrong platform | prebuild-install or node-pre-gyp downloaded wrong binary |
Force recompile: npm rebuild --build-from-source |
| Missing shared library dependency | libc, libssl, or other system library missing (common in Alpine Linux) | Install missing system libraries; use a different base image |
| Docker multi-arch build mismatch | Image built on x86_64 CI deployed to arm64 server (Raspberry Pi, AWS Graviton) | Use multi-arch Docker builds; run npm install inside the container |
Cause 1 – Node.js version upgrade without rebuilding
This is the most common cause. Native addons in node_modules were compiled
when an older Node.js version was active. After upgrading Node.js (via nvm, brew, the
official installer, or a CI base image update), the compiled .node files have
a stale NODE_MODULE_VERSION that does not match the new runtime.
# Check your current Node.js version
node --version
# Run npm rebuild to recompile all native addons against the current Node.js
npm rebuild
# Rebuild only a specific package (faster if you know which one)
npm rebuild bcrypt
npm rebuild sharp
npm rebuild canvas
# Force rebuild even when npm thinks it is current
npm rebuild --update-binary
nvm use 20 does NOT
automatically rebuild native addons. Always run npm rebuild after switching
Node.js versions with nvm. Consider adding it to a .nvmrc post-switch hook.
Cause 2 – node_modules compiled on a different machine
Copying a node_modules directory between machines, Docker layers, or CI
artifacts carries compiled binaries that may have a different Node.js version or CPU
architecture baked in. Native addons are not portable across Node.js versions or CPU
architectures.
# WRONG — never copy node_modules across environments:
# COPY node_modules /app/node_modules ← breaks if versions differ
# CORRECT — install inside the target environment:
COPY package.json package-lock.json /app/
RUN npm ci # installs and compiles for the container's Node.js
If npm rebuild alone does not fix the error (e.g., the package lock is stale
or the prebuilt binary cache is corrupt), do a full clean reinstall:
# Full clean reinstall — the most reliable fix
rm -rf node_modules package-lock.json
npm install
# On Windows (Command Prompt):
rmdir /s /q node_modules
del package-lock.json
npm install
# With npm ci (recommended in CI — uses lock file exactly):
rm -rf node_modules
npm ci
Cause 3 – Apple Silicon (arm64) vs x86_64 architecture mismatch
On Apple Silicon Macs (M1/M2/M3), Node.js can run natively as arm64 or under
Rosetta 2 as x86_64. A .node binary compiled for one architecture
cannot be loaded by a Node.js process running as the other. The error will contain a
message like "mach-o file, but is an incompatible architecture (have 'x86_64', need 'arm64e')".
# Check Node.js architecture
node -e "console.log(process.arch)"
# arm64 ← native Apple Silicon
# x64 ← running under Rosetta or on Intel
# Check npm architecture (should match Node.js)
node -e "console.log(process.arch, process.platform)"
# Check what arch a .node binary was built for (macOS)
file node_modules/sharp/build/Release/sharp-darwin-arm64v8.node
# Output example: Mach-O 64-bit dynamically linked shared library arm64
Fixing the arch mismatch
# Option A: Install native arm64 Node.js and reinstall
# Download from https://nodejs.org — choose the Apple Silicon (ARM64) installer
# Or with nvm (after installing nvm for arm64):
arch -arm64 nvm install 20
arch -arm64 nvm use 20
npm install
# Option B: If you need x86 Node.js (Rosetta), ensure everything runs under Rosetta:
arch -x86_64 node --version
arch -x86_64 npm install
# Option C: For packages like sharp that ship arch-specific prebuilts, reinstall:
npm uninstall sharp
npm install sharp # downloads the correct prebuilt for your arch
npm install, the resulting
.node binaries are compiled for x86_64. If you then open a native arm64
terminal and run the same project, Node.js sees arm64 but the binaries expect x86_64
and throws ERR_DLOPEN_FAILED. Keep your development environment fully
on one architecture.
Affected packages on Apple Silicon
| Package | Apple Silicon note | Fix |
|---|---|---|
sharp |
Ships arch-specific prebuilts; must match runtime arch | npm uninstall sharp && npm install sharp |
canvas |
May need Homebrew cairo and pango for arm64 |
brew install cairo pango && npm rebuild canvas |
bcrypt |
Needs Xcode Command Line Tools for compilation | xcode-select --install && npm rebuild bcrypt |
serialport |
Node-API based; recompile needed after arch switch | npm rebuild serialport |
node-sass |
Deprecated; no arm64 prebuilts for older versions | Migrate to sass (Dart Sass — pure JS, no native addon) |
Cause 4 – Electron: wrong NODE_MODULE_VERSION
Electron bundles its own Node.js runtime. The NODE_MODULE_VERSION Electron
uses is different from (and not synchronized with) the system Node.js version of the same
major. Running npm rebuild compiles addons for the system Node.js,
not for Electron's internal Node.js, so the binary is still incompatible at Electron
runtime.
# Install electron-rebuild as a dev dependency
npm install --save-dev electron-rebuild
# Rebuild all native addons for Electron's Node.js version
./node_modules/.bin/electron-rebuild
# Or with npx:
npx electron-rebuild
# Rebuild a single package:
npx electron-rebuild -f -w bcrypt
# Add to package.json so it runs automatically after npm install:
{
"scripts": {
"postinstall": "electron-rebuild"
}
}
NODE_MODULE_VERSION it uses. Check
releases.electronjs.org
to see the exact Node.js version bundled in your Electron version.
electron-rebuild reads this automatically from the installed Electron package.
Cause 5 – Prebuilt binaries: prebuild-install and node-pre-gyp
Many popular packages (sharp, bcrypt, sqlite3, canvas, serialport) use
node-pre-gyp or prebuild-install to download a pre-compiled
binary for your Node.js version and platform at install time, avoiding a local compilation.
If the download fails silently, or the wrong binary is fetched (wrong Node.js version,
wrong OS, wrong architecture), you get ERR_DLOPEN_FAILED when the package
is loaded.
# Force recompilation from source instead of using a prebuilt binary:
npm rebuild <package> --build-from-source
# Example for specific packages:
npm rebuild bcrypt --build-from-source
npm rebuild sqlite3 --build-from-source
# With node-pre-gyp directly:
./node_modules/.bin/node-pre-gyp rebuild --directory node_modules/<package>
# Check which binary was downloaded and whether it matches:
node -e "const b = require('bcrypt'); console.log(b)"
prebuild-install may fail to download the prebuilt binary. The package then
falls back to compiling from source with node-gyp, which requires Python,
make, and a C++ compiler. If those build tools are not present, you get a different error.
Set npm config set build-from-source true to always compile locally, or
mirror the prebuilt binaries in a private registry.
Cause 6 – Docker and CI: architecture and version mismatch
CI/CD pipelines are a frequent source of ERR_DLOPEN_FAILED because the
build environment (Node.js version, OS, CPU architecture) often differs from the
production environment.
Dockerfile: always install inside the container
# Good Dockerfile pattern — never copy pre-built node_modules
FROM node:20-alpine
WORKDIR /app
# Copy manifests only — then install inside the container
COPY package.json package-lock.json ./
RUN npm ci --only=production
COPY . .
CMD ["node", "server.js"]
Multi-arch Docker builds (arm64 + x86_64)
# Build for multiple architectures — Docker handles per-arch installs automatically
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t myapp:latest \
--push .
# In the Dockerfile, npm ci runs inside each arch's layer so binaries are correct
Alpine Linux — missing glibc
Many prebuilt .node binaries are compiled against glibc (GNU C Library).
Alpine Linux uses musl libc instead. Loading a glibc binary on Alpine produces
ERR_DLOPEN_FAILED with "invalid ELF header" or
"cannot open shared object file".
# Option A: Use the node:20-bookworm-slim (Debian) base image instead of Alpine:
FROM node:20-bookworm-slim
# Option B: Add glibc compatibility layer to Alpine (not recommended for production):
RUN apk add --no-cache libc6-compat
# Option C: Force compilation from source (requires build tools):
RUN apk add --no-cache python3 make g++ \
&& npm ci --build-from-source
GitHub Actions and CI: matching versions
# .github/workflows/deploy.yml
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node@v4
with:
node-version: '20' # Must match production Node.js version exactly
- run: npm ci
# Do NOT cache node_modules across jobs with different Node.js versions
node_modules between
CI runs for speed, the cache key must include the Node.js version. Otherwise a
cached build from an old Node.js version will be restored and cause ERR_DLOPEN_FAILED.
key: node-modules-${{ runner.os }}-${{ matrix.node }}-${{ hashFiles('package-lock.json') }}
Cause 7 – Missing system shared libraries
Some native addons link against system-level shared libraries (libssl, libcairo, libpango,
libsqlite3, etc.). If those libraries are absent or on a different version, dlopen()
fails with an "cannot open shared object file" or "symbol not found" message
inside the ERR_DLOPEN_FAILED error.
# Diagnose missing shared libraries on Linux:
ldd node_modules/canvas/build/Release/canvas.node
# Look for lines saying: "not found" — those are the missing libraries
# Diagnose on macOS:
otool -L node_modules/canvas/build/Release/canvas.node
# Install missing libraries for canvas on Debian/Ubuntu:
sudo apt-get install -y libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev
# Install missing libraries for canvas on macOS (Homebrew):
brew install cairo pango libpng jpeg giflib librsvg
# Install missing libraries for sharp on Alpine:
apk add --no-cache vips-dev
Affected Popular Packages
| Package | What it does | Fix command | Notes |
|---|---|---|---|
bcrypt |
Password hashing | npm rebuild bcrypt |
Consider bcryptjs (pure JS, no native addon) |
sharp |
Image processing | npm uninstall sharp && npm install sharp |
Uses libvips; arch-specific prebuilts |
canvas |
HTML5 Canvas for Node.js | npm rebuild canvas |
Requires Cairo, Pango system libraries |
sqlite3 |
SQLite database bindings | npm rebuild sqlite3 |
Consider better-sqlite3 (also native, but faster) |
better-sqlite3 |
Synchronous SQLite bindings | npm rebuild better-sqlite3 |
Also native; same rebuild requirement |
node-sass |
Sass compiler (deprecated) | Migrate to sass (Dart Sass) |
No arm64 prebuilts for older versions; no longer maintained |
serialport |
Serial port communication | npm rebuild serialport |
Node-API based; relatively stable across Node.js versions |
fsevents |
macOS file system events | npm rebuild fsevents |
macOS only; installed as optional dependency |
Safe Rebuild Patterns
After every Node.js version switch
# With nvm — rebuild immediately after switching
nvm use 20
npm rebuild
# Verify the rebuild succeeded by requiring the native addon:
node -e "require('bcrypt'); console.log('bcrypt OK')"
node -e "require('sharp'); console.log('sharp OK')"
Add npm rebuild to postinstall for Electron
// package.json
{
"scripts": {
"postinstall": "electron-rebuild",
"rebuild": "electron-rebuild -f"
},
"devDependencies": {
"electron": "^29.0.0",
"electron-rebuild": "^3.2.9"
}
}
Graceful fallback: pure-JS alternatives
// Instead of bcrypt (native), use bcryptjs (pure JS):
// npm install bcryptjs
const bcrypt = require('bcryptjs'); // drop-in API replacement
// Instead of node-sass (native), use sass (Dart Sass, pure JS):
// npm install sass
const sass = require('sass');
// Instead of sqlite3 (native) for simple use cases:
// npm install sql.js ← pure JS SQLite compiled to WebAssembly
Debugging Checklist
- Read the error message — note the compiled
NODE_MODULE_VERSIONand the required one. The difference tells you which Node.js versions are involved. - Run
node --versionand compare to the Node.js version used whennpm installwas last run. - Run
node -e "console.log(process.arch)"— confirm the architecture matches the compiled binaries (especially on Apple Silicon). - Try
npm rebuildfirst. If it errors, note the specific error — it may indicate missing build tools (Python, make, C++ compiler). - If
npm rebuildfails, do a full clean reinstall:rm -rf node_modules package-lock.json && npm install. - In an Electron project, use
electron-rebuildinstead ofnpm rebuild. - In Docker: ensure
npm ciruns inside the container — never copynode_modulesfrom the host. - On Alpine Linux: check for missing glibc by running
ldd <path-to-.node>and look for "not found" lines. - Check if the package has a pure-JS fallback you can use instead (e.g.,
bcryptjsinstead ofbcrypt). - If using
prebuild-installornode-pre-gyp, force source compilation:npm rebuild <pkg> --build-from-source.
ERR_DLOPEN_FAILED for any developer or CI runner using a different Node.js
version or CPU architecture. Ensure node_modules is in your .gitignore.
Frequently Asked Questions
What is ERR_DLOPEN_FAILED in Node.js?
ERR_DLOPEN_FAILED is thrown when Node.js cannot load a native addon (.node binary) via the OS dlopen() call. The most common cause is an ABI mismatch: the addon was compiled for a different NODE_MODULE_VERSION than the running Node.js. The full error reads: The module '...binding.node' was compiled against a different Node.js version using NODE_MODULE_VERSION X. This version of Node.js requires NODE_MODULE_VERSION Y.
How do I fix ERR_DLOPEN_FAILED?
Step 1: run npm rebuild in your project root. Step 2: if rebuild fails, delete node_modules and package-lock.json and run npm install. Step 3: for Electron projects, use electron-rebuild instead. Step 4: on Apple Silicon, verify node -e "console.log(process.arch)" matches how node_modules was compiled, and reinstall if needed.
Why does ERR_DLOPEN_FAILED happen after upgrading Node.js?
Native addons (.node binaries) are compiled against a specific NODE_MODULE_VERSION. Every major Node.js release increments this integer (Node 18 = 108, Node 20 = 115, Node 22 = 127). After upgrading Node.js, the existing compiled binaries have the old version number and Node.js refuses to load them. Run npm rebuild to recompile all native addons for the new version.
Why does ERR_DLOPEN_FAILED happen in production but not locally?
The most common reasons: (1) your local and production Node.js versions differ — the addon was compiled locally for one version, deployed to production with another; (2) your local machine is x86_64 but production is arm64 (AWS Graviton, Raspberry Pi); (3) you copied node_modules into the Docker image from the host instead of running npm ci inside the container; (4) the CI cache restored node_modules built with an old Node.js version.
How do I fix ERR_DLOPEN_FAILED on Apple Silicon (M1/M2/M3)?
Run node -e "console.log(process.arch)". If you see x64 on an Apple Silicon Mac, Node.js is running under Rosetta. Install a native arm64 Node.js (from the official website or via arch -arm64 nvm install 20). Then reinstall your packages: rm -rf node_modules && npm install. The key rule is: Node.js architecture and the compiled .node binaries must match — both arm64 or both x64, not mixed.
How do I fix ERR_DLOPEN_FAILED in Electron?
Electron bundles its own Node.js with a different NODE_MODULE_VERSION than the system Node.js. Running npm rebuild targets the system Node.js, not Electron's. Use electron-rebuild instead: npm install --save-dev electron-rebuild && ./node_modules/.bin/electron-rebuild. Add a "postinstall": "electron-rebuild" script to package.json so it runs automatically after every npm install.
How do I fix ERR_DLOPEN_FAILED in Docker or CI?
Run npm ci inside the Docker container — never copy pre-built node_modules from the host. Ensure the Docker base image Node.js version matches production. For multi-arch deployments, use docker buildx build --platform linux/amd64,linux/arm64. In CI, include the Node.js version in the cache key so a stale cache from a different version is never restored.
What are prebuild-install and node-pre-gyp and how do they cause ERR_DLOPEN_FAILED?
prebuild-install and node-pre-gyp are tools that download pre-compiled .node binaries from GitHub Releases or S3 during npm install, saving users from having to compile from source. If the download fails or fetches the wrong binary (wrong Node.js version, wrong OS, wrong architecture), the corrupt or mismatched binary causes ERR_DLOPEN_FAILED when loaded. Fix: force source compilation with npm rebuild <package> --build-from-source.
How do I avoid ERR_DLOPEN_FAILED on Alpine Linux?
Alpine Linux uses musl libc instead of glibc. Most prebuilt .node binaries are compiled against glibc and cannot be loaded on Alpine, causing ERR_DLOPEN_FAILED with "invalid ELF header". Solutions: (1) switch to a Debian-based image (node:20-bookworm-slim); (2) add apk add --no-cache libc6-compat for basic compatibility; (3) compile from source inside Alpine by adding build tools: apk add python3 make g++ and running npm ci --build-from-source.
Related Errors
MODULE_NOT_FOUND— Node.js cannot find the module at all (not an ABI problem; file is missing or path is wrong)ERR_REQUIRE_ESM— loading a pure ES module withrequire(); a different module-loading failureERR_PACKAGE_PATH_NOT_EXPORTED— the package'sexportsmap does not expose the subpath being required