TypeScript

Fix error:0308010C digital envelope routines::unsupported

Find which part of your build still asks for MD4, apply the one-line workaround to unblock a deploy, then remove it with a webpack or react-scripts upgrade.

Mahdi Ben RhoumaMahdi Ben Rhouma
8 min read

TL;DR#

error:0308010C:digital envelope routines::unsupported (code ERR_OSSL_EVP_UNSUPPORTED) is thrown when webpack 4 asks Node's crypto module for an MD4 hash. Node.js 17 and later bundle OpenSSL 3, which no longer allows MD4 by default. The durable fix is to upgrade the build tool. The last webpack 4 release added its own MD4 for Node 18 and later, webpack 5 has shipped a WebAssembly MD4 for a long time, and react-scripts 5 depends on webpack 5. The quick workaround, NODE_OPTIONS=--openssl-legacy-provider, also works, but it switches legacy algorithms back on for the whole process.

The error, as reported on Stack Overflow by someone running a React app in a node:18-alpine image:

text
Error: error:0308010C:digital envelope routines::unsupported
    at new Hash (node:internal/crypto/hash:71:19)
    at Object.createHash (node:crypto:133:10)
    at module.exports (/app/node_modules/webpack/lib/util/createHash.js:135:53)
    at NormalModule._initBuildHash (/app/node_modules/webpack/lib/NormalModule.js:417:16)

The same failure was reproduced on 4 October on the official node:22 image, with the webpack 4 release just before the fix and a one-line entry file. The frames inside node:internal moved, the webpack frames did not:

text
v22.23.3
openssl 3.5.8
4.46.0
Error: error:0308010C:digital envelope routines::unsupported
    at new Hash (node:internal/crypto/hash:101:19)
    at Object.createHash (node:crypto:139:10)
    at module.exports (/app/node_modules/webpack/lib/util/createHash.js:135:53)
    at NormalModule._initBuildHash (/app/node_modules/webpack/lib/NormalModule.js:417:16)

Why OpenSSL 3 rejects webpack 4's hash#

Webpack names chunks and caches modules by hashing their content. Webpack 4 does this with an MD4 hash from Node's crypto module, which is exactly where the stack trace points: webpack/lib/util/createHash.js, called from NormalModule._initBuildHash. Nothing in your code or your loaders is wrong. The first module webpack builds triggers it.

The Node.js 17.0.0 release notes explain the other half. Node 17 moved to OpenSSL 3.0, and "if you hit an ERR_OSSL_EVP_UNSUPPORTED error in your application with Node.js 17, it's likely that your application or a module you're using is attempting to use an algorithm or key size which is no longer allowed by default with OpenSSL 3.0". MD4 is one of those algorithms. OpenSSL 3 moved it into a separate legacy provider that is not loaded unless something asks for it.

You can confirm it in one line, with no webpack involved:

bash
docker run --rm node:22 node -e "const c=require('crypto');for(const a of ['md4','sha256']){try{c.createHash(a);console.log(a,'ok')}catch(e){console.log(a,e.code)}}"
text
md4 ERR_OSSL_EVP_UNSUPPORTED
sha256 ok

That is why the error often shows up "only in Docker". The image runs a newer Node than the developer's laptop, so the same package.json meets a different OpenSSL. In the Stack Overflow report, the Dockerfile starts with FROM node:18-alpine, which already means OpenSSL 3. As a negative control, the same project built on node:16-alpine finishes without error:

text
v16.20.2
openssl 1.1.1v+quic
Version: webpack 4.46.0
main.js  963 bytes       0  [emitted]  main

The project, the lockfile and the command were identical. Only the Node major changed, so the runtime's OpenSSL decides, not the project.

Fix: upgrade the part of the build that still asks for MD4#

1. Find which webpack you actually run#

bash
npm ls webpack

npm ls prints the installed webpack version and who pulls it in. Two cases cover almost every report:

  • webpack is your own dependency: a hand-written webpack.config.js, an older Storybook, Vue CLI or Laravel Mix setup;
  • webpack comes through react-scripts (Create React App). The registry shows that the last react-scripts 4 release pins webpack to an exact version from before the fix:
bash
npm view react-scripts@4.0.3 dependencies.webpack
text
4.44.2

2a. Your own webpack 4: move to the last 4.x release#

The last webpack 4 release has a single entry in its notes, added by PR #17628:

bash
gh release view v4.47.0 -R webpack/webpack
text
tag:	v4.47.0
* [Security] - Add support for md4 in Node >=18. by @iclanton in https://github.com/webpack/webpack/pull/17628
bash
npm install --save-dev webpack@4.47.0

In the reproduction, changing only the webpack version turned the failing Node 22 build into a passing one, with no flag and no environment variable:

text
Version: webpack 4.47.0
main.js  963 bytes       0  [emitted]  main

Webpack 5 solves it as well, from the release whose notes, on the webpack releases page, read:

text
tag:	v5.61.0
* use a wasm md4 implementation for node 17 support

Only take that route if you have time for the full webpack 5 migration (loaders, Node polyfills, config changes). The 4.x patch is enough to stop this error.

2b. Create React App: move to react-scripts 5#

react-scripts 5 depends on a webpack 5 range that starts after the WebAssembly MD4 change:

bash
npm view react-scripts@5.0.1 dependencies.webpack
text
^5.64.4
bash
npm install react-scripts@5.0.1

Moving from react-scripts 4 to 5 is a major upgrade with its own breaking changes, listed in the Create React App changelog. If you cannot take that on today, use step 3 and plan the upgrade. Create React App's tracking issue for this error is react/create-react-app#11708. The Stack Overflow answer links it under the repository's old facebook/ path.

3. Workaround to unblock a build: the legacy provider#

The Node.js CLI documentation sums up --openssl-legacy-provider in one line: "Enable OpenSSL 3.0 legacy provider." The 17.0.0 release notes call it "a temporary workaround". There are two ways to pass it.

As an environment variable. This works for any tool and is the simplest change in a Dockerfile:

dockerfile
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
ENV NODE_OPTIONS=--openssl-legacy-provider
CMD ["npm", "start"]

In the reproduction, setting only that variable on the failing build made it pass on Node 22:

text
main.js  963 bytes       0  [emitted]  main
Entrypoint main = main.js

On a CI service or a hosting platform, add the same NODE_OPTIONS value as a build environment variable.

As a react-scripts argument. This works because react-scripts passes every argument placed before the script name to node itself (see nodeArgs in its bin script):

json
{
  "scripts": {
    "start": "react-scripts --openssl-legacy-provider start",
    "build": "react-scripts --openssl-legacy-provider build"
  }
}

Either way, the flag applies to the whole Node process. Every package in the build gets the legacy algorithms back, not only webpack. That is why it belongs in a commit that also schedules its own removal.

Verify the fix#

Run the build in the same image as production, not on your laptop. The Dockerfile above only starts the app, so run the build script explicitly:

bash
docker build -t my-app .
docker run --rm my-app node --version
docker run --rm my-app npm run build

The build should finish with an emitted asset list, and the stack trace through NormalModule._initBuildHash should be gone. If you used the workaround, remove --openssl-legacy-provider after the upgrade and confirm the build still passes without it. That proves the upgrade fixed the error, not the flag.

One email a month — the latest explained errors

What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.

When the trace does not go through webpack#

Read the frames below node:crypto in your own trace. The error is OpenSSL refusing an algorithm, and webpack 4 is only the most common caller. The first frame under Object.createHash names the package that asked for the hash. If it is not webpack/lib/util/createHash.js, that package is the one to upgrade: run npm ls <package> to see who depends on it, then look in its changelog for an OpenSSL 3 or Node 17 fix. Until one exists, the legacy provider from step 3 unblocks the build in the same way.

Downgrading Node to 16 also hides the error, and the negative control above shows why. But Node 16 is end of life, and frameworks now refuse it: Next.js 15, for example, stops with a hard minimum Node.js version error on older runtimes. That swaps one blocking error for another.

The next error after the upgrade#

Upgrading an old build chain to a newer Node often brings up the next incompatibility. If the build in the new image now stops at sh: tsc: not found, a production-only install skipped a devDependency. The tsc not found in Docker guide covers both that case and binaries that installed but never got linked. If a newer dependency fails with a "Package subpath is not defined by exports" error, its exports map gives nothing for the path you import, or nothing for require(). The package subpath exports fix shows how to tell those two cases apart.

The order of preference stays the same in every case. Fix the tool that asks for an old algorithm, keep the runtime on a supported Node line, and treat --openssl-legacy-provider as a dated workaround you are expected to remove, not as configuration you keep.

Further reading: Fix Invalid options object: allowedHosts[0] with CRA proxy. Also see Fix secretOrPrivateKey must have a value in NestJS JWT, Vite server running but not working on localhost: 3 causes.

Frequently Asked Questions

|

Have more questions? Contact us

Still stuck? Paste your error.

Paste the error message or stack trace — we check it against every fix and tool on the site.

Matching happens in your browser. When you search, a cleaned error signature is sent so we can spot errors we do not cover yet — never the raw text, your IP address or your e-mail. Do not paste passwords or keys. Details

Written by

Mahdi Ben Rhouma
Mahdi Ben Rhouma

Full-Stack Dev — Next.js & Supabase

Solo developer building SaaS products with Next.js and Supabase. Writing about production patterns the official docs skip.

Remote