Node.js Error

ERR_DLOPEN_FAILED

error code: ERR_DLOPEN_FAILED

Complete reference for Error: The module '...binding.node' was compiled against a different Node.js version — every cause, every fix, covering npm rebuild, Apple Silicon arm64, Docker/CI, Electron, and prebuilt binaries.

Quick Answer: Node.js throws 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'
}
Exact error strings you will see:
was compiled against a different Node.js version using NODE_MODULE_VERSION
Error: dlopen failed, was compiled for a different Node.js version
mach-o file, but is an incompatible architecture (have 'x86_64', need 'arm64e')
invalid ELF header
cannot 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.x83End-of-life
Node.js 16.x93End-of-life
Node.js 18.x108Maintenance LTS
Node.js 20.x115Active LTS
Node.js 22.x127Current
Node.js 23.x131Current

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
With nvm: Switching versions with 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
Rosetta caveat: Mixing architectures is the problem. If your terminal runs under Rosetta (x86_64) and you run 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"
  }
}
Electron version mapping: Each Electron release documents which Node.js version and 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)"
Network-blocked environments: In corporate or air-gapped environments, 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
Cache invalidation in CI: If you cache 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

  1. Read the error message — note the compiled NODE_MODULE_VERSION and the required one. The difference tells you which Node.js versions are involved.
  2. Run node --version and compare to the Node.js version used when npm install was last run.
  3. Run node -e "console.log(process.arch)" — confirm the architecture matches the compiled binaries (especially on Apple Silicon).
  4. Try npm rebuild first. If it errors, note the specific error — it may indicate missing build tools (Python, make, C++ compiler).
  5. If npm rebuild fails, do a full clean reinstall: rm -rf node_modules package-lock.json && npm install.
  6. In an Electron project, use electron-rebuild instead of npm rebuild.
  7. In Docker: ensure npm ci runs inside the container — never copy node_modules from the host.
  8. On Alpine Linux: check for missing glibc by running ldd <path-to-.node> and look for "not found" lines.
  9. Check if the package has a pure-JS fallback you can use instead (e.g., bcryptjs instead of bcrypt).
  10. If using prebuild-install or node-pre-gyp, force source compilation: npm rebuild <pkg> --build-from-source.
Never commit node_modules to Git. Committed native binaries will cause 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