After upgrading Node, a build that worked yesterday fails with an OpenSSL error:
Error: error:0308010C:digital envelope routines::unsupported
at new Hash (node:internal/crypto/hash:103:19)
at Object.createHash (node:crypto:146:10)
...
opensslErrorStack: [
'error:03000086:digital envelope routines::initialization error',
'error:0308010C:digital envelope routines::unsupported'
],
library: 'digital envelope routines',
reason: 'unsupported',
code: 'ERR_OSSL_EVP_UNSUPPORTED'
}
Nothing in your project changed. Node 17 upgraded its bundled OpenSSL to version 3, and OpenSSL 3 moved a set of old algorithms — MD4 among them — into an optional "legacy provider" that is disabled by default. Webpack 4 hashes module identifiers with MD4. The moment Node stopped offering MD4, every webpack 4 build started failing this way.
Check which OpenSSL you are on:
node -p "process.versions.openssl"
# 3.6.2 → legacy provider off by default; this error is possible
# 1.1.1x → Node 16 or earlier; unaffected
Why This Happens
createHash('md4') used to work because OpenSSL 1.1 offered every algorithm it shipped. Under OpenSSL 3, providers gate them, and the legacy provider must be loaded explicitly. You can see the whole mechanism in two commands:
node -e "require('crypto').createHash('md4')"
# Error: error:0308010C:digital envelope routines::unsupported
node --openssl-legacy-provider -e "console.log(require('crypto').createHash('md4').update('a').digest('hex'))"
# bde52cb31de33e46245e05fbdbd6fb24
The error code itself is unhelpfully generic — 0308010C names an OpenSSL subsystem, not the algorithm. The algorithm is in the stack trace, in the frame that called createHash.
Match your situation before choosing a fix:
| What you are running | Cause | Fix |
|---|---|---|
| webpack 4, or a tool that bundles it | MD4 default hash | Fix 1 — upgrade |
react-scripts 4 or earlier | webpack 4 underneath | Fix 1 — upgrade |
| webpack 5, error persists | Explicit md4 in config or a plugin | Fix 2 — change the hash function |
Your own createHash('md4') | Legacy algorithm in your code | Fix 3 — switch algorithm |
| Cannot upgrade right now | — | Fix 4 — the legacy flag, temporarily |
Fix 1: Upgrade the Bundler (The Real Fix)
Webpack 5 uses a supported hash function, so upgrading removes the cause rather than working around it:
npm install --save-dev webpack@5 webpack-cli@latest
For create-react-app, the equivalent is react-scripts 5, which moved to webpack 5:
npm install --save-dev react-scripts@latest
Check what you are actually on before and after — the bundler is often a transitive dependency you never installed directly:
npm ls webpack
Other toolchains have their own thresholds: Vue CLI 5, Angular 12+, and Next.js 12+ are all on webpack 5 or their own bundler and are unaffected. If you are on an older major of any of those, upgrading the framework is the path.
Fix 2: webpack 5 and Still Failing
Webpack 5 does not use MD4 by default, so if the error survives the upgrade, something is asking for it explicitly. Search your configuration:
grep -rn "md4\|hashFunction" webpack.config.js config/ 2>/dev/null
Set a supported algorithm if you find one:
// webpack.config.js
module.exports = {
output: {
hashFunction: 'xxhash64', // or 'sha256'
},
};
If nothing in your own config matches, a plugin or loader is responsible. The stack trace names it — read the frames just below Object.createHash and upgrade that package.
Fix 3: MD4 in Your Own Code
If your code calls it directly, the fix is to choose a different algorithm — and which one depends on what the hash is for:
const crypto = require('crypto');
crypto.createHash('md4'); // fails on OpenSSL 3
crypto.createHash('sha256'); // use for anything security-relevant
crypto.createHash('sha1'); // still available, but not for security
MD4 has been cryptographically broken for decades. If it was protecting anything — signatures, integrity checks, password material — this error has surfaced a real problem and sha256 is the answer. If it was only ever a fast non-cryptographic cache key, xxhash64 or sha1 is fine.
Fix 4: The Legacy Provider Flag (Temporary)
When you cannot upgrade today, re-enable the legacy algorithms:
export NODE_OPTIONS=--openssl-legacy-provider
npm run build
Make it survive across contributors by putting it in package.json rather than everyone's shell:
{
"scripts": {
"start": "cross-env NODE_OPTIONS=--openssl-legacy-provider react-scripts start",
"build": "cross-env NODE_OPTIONS=--openssl-legacy-provider react-scripts build"
}
}
cross-env is worth the dependency here — the bare NODE_OPTIONS=... command form is shell syntax that PowerShell and cmd do not accept. Without it, Windows contributors get a different, more confusing failure. The manual equivalents:
$env:NODE_OPTIONS="--openssl-legacy-provider" # PowerShell
set NODE_OPTIONS=--openssl-legacy-provider :: cmd
Treat this as a stopgap with a deadline. The flag re-enables broken algorithms for the whole process, so never set it on a server that also performs real cryptography — you would be re-enabling MD4 and friends for your application code, not just your bundler. It is defensible for a build step on a CI runner. It is not defensible in production runtime configuration, and it is not a substitute for the upgrade.
Verify the Fix
Run the build that was failing:
npm run build
Confirm you are no longer relying on the flag — this should now succeed with it explicitly unset:
env -u NODE_OPTIONS npm run build
And confirm the bundler you expect is the one being used:
npm ls webpack
node -p "process.versions.openssl"
If the build passes without NODE_OPTIONS, the underlying cause is genuinely gone and you can delete the flag from your scripts.
Prevention
- Upgrade the bundler rather than carrying the flag. Every month the flag stays in
package.json, it becomes more likely someone copies it into a runtime environment. - Pin Node in
.nvmrcand inenginesso a developer or CI runner cannot silently jump a major version mid-project. - Read
process.versions.opensslin CI when debugging a build that works locally and fails on the runner — an OpenSSL 1.1 laptop and an OpenSSL 3 runner produce exactly this split. - Never use MD4 or MD5 for anything security-relevant. If this error caught one in your own code, the correct response is
sha256, not the legacy provider.