error:0308010C digital envelope routines::unsupported — Fixing Node 17+ Breakage in Older Projects
Symptom: worked yesterday, npm run dev explodes today
Error: error:0308010C:digital envelope routines::unsupported
at new Hash (node:internal/crypto/hash:71:19)
at Object.createHash (node:crypto:130:10)
at BulkCacheDecorator.cheaderHash (...) ← inside webpack 4
One-line diagnosis: your Node version is 17+, and the webpack 4 inside the project still uses MD4 — a hash algorithm that OpenSSL 3.0 removed from its defaults. Your application code is usually fine; the runtime and the bundler disagree.
Common triggers:
- New machine or reinstall;
nvm/fnmdefaulted to the latest LTS (18 / 20 / 22) - CI base image upgrade (
node:16→node:20) - A Dockerfile that says
FROM node:latest - Inheriting a legacy project (Vue CLI 4, create-react-app 4, raw webpack 4)
Confirm with:
node -v
npm ls webpack
If Node ≥ 17 and webpack is 4.x, you are in this failure class.
Root cause: OpenSSL 3.0 vs MD4
Starting with Node 17, Node bundles OpenSSL 3.0. OpenSSL 3.0 moved legacy algorithms like MD4 into the "legacy provider," which is not loaded by default. webpack 4 computes module hashes with crypto.createHash('md4') — so the first call throws.
Decode the hex in the error: 0308010C is ERR_OSSL_EVP_UNSUPPORTED. It is not only webpack 4 — old grunt, gulp-rev, and some crypto-js releases hit the same wall.
Fix 1: enable the legacy provider (fast unblock)
Pass --openssl-legacy-provider so Node loads MD4 again:
# macOS / Linux
export NODE_OPTIONS=--openssl-legacy-provider
npm run dev
# Windows CMD
set NODE_OPTIONS=--openssl-legacy-provider
# Windows PowerShell
$env:NODE_OPTIONS="--openssl-legacy-provider"
The durable version lives in package.json (requires cross-env as a devDependency):
{
"scripts": {
"dev": "cross-env NODE_OPTIONS=--openssl-legacy-provider vue-cli-service serve",
"build": "cross-env NODE_OPTIONS=--openssl-legacy-provider vue-cli-service build"
}
}
When it fits: the project has a short remaining life, or you need a green build today. Tradeoff: you keep running on a deprecated hash path — fine as a bridge, not as a destination. The flag still works on current LTS lines, including Node 22, but it does not remove the underlying debt.
Fix 2: upgrade to webpack 5 (the durable fix)
| Project type | Upgrade path |
|---|---|
| Vue CLI 4 | vue upgrade to Vue CLI 5 (webpack 5 built in) |
| CRA 4 | migrate to CRA 5, or jump to Vite |
| Hand-rolled webpack 4 | npm i webpack@5 webpack-cli@4 -D, then walk breaking changes |
webpack 5 switched default hashing to xxhash64 / sha256, compatible with OpenSSL 3. You also get persistent caching and faster production builds.
Post-upgrade gotcha: missing assert / buffer / process — webpack 5 no longer auto-polyfills Node core modules. Add them via resolve.fallback or npm shims as errors surface.
Fix 3: pin an older Node (stopgap only)
nvm install 16
nvm use 16
node -v # v16.x
Add a .nvmrc at the repo root and mirror it in CI / Docker:
16
FROM node:16-alpine
Warning: Node 16 reached end-of-life in September 2023 and receives no security patches. Use it only as a bridge while the webpack 5 (or Vite) migration is scheduled — never as a destination.
Choosing between the three
| Fix | Cost | Risk | Typical use |
|---|---|---|---|
--openssl-legacy-provider | Minutes | Low (process-scoped) | Unblock today, short maintenance window |
| Upgrade webpack 5 | Hours–days | Medium (build drift) | Projects that still ship features |
| Pin EOL Node | Minutes | High (no security patches) | Temporary CI parity while upgrading |
Node 22, Next.js, and adjacent stacks
- Next.js App Router / Turbopack projects on modern Next rarely hit this — they are not on webpack 4. If you see
0308010Cinside a monorepo, check which package still pulls webpack 4 (npm ls webpackfrom the failing workspace). - Custom webpack in Next (
next.config.js→webpack) can reintroduce old plugins that call legacy hashes — audit those plugins first. - Electron / NW.js builds that embed Node 17+ with an old packager show the same OpenSSL refusal; the same three fixes apply inside the packager's Node, not only on the host CLI.
- Prefer locking the toolchain with
.nvmrc+engines+ (optional) Volta /packageManagerso every laptop and CI share one story.
Prevention: nail the Node version to the project
The underlying problem is runtime drift. Three lines of defense:
.nvmrcplusenginesinpackage.json:
{
"engines": { "node": ">=20 <21" }
}
- Enforce it in CI (
engine-strict=truein.npmrc) so a wrong Node fails loudly instead of producing cryptic crypto errors - Never use
node:latestin a Dockerfile — pin the major (and preferably minor) version
Why this error became an epidemic in 2022–2023
OpenSSL 3.0 removed legacy algorithms (MD4 among them) from the default provider — webpack 4's hashing used MD4, and every Node 17+ run of a webpack 4 project hit this at once. The message names none of that ("digital envelope routines::unsupported" — a provider-level refusal phrased as a crypto mystery). Priority order: upgrade the bundler (removes the MD4 dependency), legacy-provider flag (unblocks today), pin EOL Node (defers everything). Lesson: hash algorithms inside build tools are invisible dependencies — "our app doesn't use MD4" can be true of the app and false of the build.
Checklist
node -v— is it ≥17? Is webpack 4.x (npm ls webpack)?- Error contains
0308010C/digital envelope routines— confirmed diagnosis - Emergency:
NODE_OPTIONS=--openssl-legacy-providerto get running - Schedule the webpack 5 (or Vite) upgrade; pin EOL Node only as a bridge
- Re-check
.nvmrc,engines, and Dockerfile agree on the same Node major
What the legacy-provider flag does not fix
--openssl-legacy-provider re-enables algorithms OpenSSL 3 moved out of the default provider. It does not:
- Make an abandoned webpack 4 toolchain secure or supported long-term
- Fix native addons compiled against a different OpenSSL
- Replace upgrading CI images that still pull
node:16while laptops run 20
Use it as a bridge on the build machine that is failing today, then remove it when the bundler no longer needs MD4-era hashing. Leaving the flag in production Dockerfiles silently papers over the next OpenSSL policy change.
Triage notes
The fix-chooser table slices by cost, risk, and typical use, but the body repeats one sentence: the legacy provider and pinning an EOL Node are stopgaps, not destinations — the priority is upgrading the bundler to drop the MD4 dependency. The intro's trigger list (new machine, CI base-image bump, FROM node:latest) all point to the same diagnosis, environment drift, which is why all three preventive lines (.nvmrc, engine-strict, a pinned Dockerfile base image) are version pins. The epidemic section's lesson deserves a reread: hashes inside build tooling are invisible dependencies — "our app doesn't use MD4" can be true of the app and false of the build.
Provided by ToolVault. Related: ERESOLVE conflicts, npm registry mirror guide, Cannot find module. See the homepage for more developer tools.
Related Tools
Related Articles
Linux Crontab in Practice: Commands, Logs, and Debugging
Crontab beyond syntax: three must-know commands, /etc/cron.d vs user crontabs, five-step debugging, PATH/timezone/% escaping, MAILTO, and what to do when you need second-level scheduling.
docker run to docker-compose: The Complete Migration Guide (with Flag Map)
Migrate docker run commands to docker-compose.yml: five advantages of compose over raw run, a flag-by-flag mapping table (ports, volumes, env, restart, networks), the classic pitfalls (bare -e passthrough, anonymous volumes, --rm semantics), and an online converter that runs locally in your browser.
Permission denied (publickey): 6 Reasons Git Push Fails Over SSH (and the Fix for Each)
git clone or push rejected with Permission denied (publickey) fatal: Could not read from remote repository? Covers missing keygen, key not loaded in the agent, public key not added to GitHub/GitLab, multi-account key routing with ~/.ssh/config, deploy key limits, and wrong remote URLs — with ssh -v diagnostics.