Home JavaScript React OpenSSL Error Fix: 5 Proven Ways to Ship Again
Intermediate 3 min · September 07, 2026
React Node OpenSSL Error Fix

React OpenSSL Error Fix: 5 Proven Ways to Ship Again

React build fails with ERR_OSSL_EVP_UNSUPPORTED after a Node upgrade? Fix it in 60 seconds, then kill it forever.

N
Naren Founder & Principal Engineer

20+ years shipping production JavaScript and front-end systems at scale. Lessons pulled from things that broke in production.

Follow
Production
production tested
September 22, 2026
last updated
1,799
articles · all by Naren
Before you start⏱ 14 min
  • A React app built with Create React App or webpack 4
  • Node.js 17 or newer installed locally
  • Basic comfort running npm scripts from a terminal
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • React builds crash on Node 17+ because OpenSSL 3 blocks the md4 hash that webpack 4 calls via crypto.createHash('md4')
  • Three moving parts: your React code (innocent), webpack 4's hashFunction default (guilty), and Node's OpenSSL 3 provider (the enforcer)
  • Fastest unblock: NODE_OPTIONS=--openssl-legacy-provider restores builds in under 60 seconds with zero code changes
  • Performance angle: upgrading to react-scripts 5 / webpack 5 swaps md4 for xxhash64 and cuts rebuild hashing time by roughly 30% on large apps
  • Permanent cures ranked: upgrade react-scripts to 5+, migrate to Vite (50-80% faster cold builds), never pin yourself to EOL Node 16
  • Production trap: a global NODE_OPTIONS export in .bashrc leaks the legacy provider into every project and hides the next crypto failure for months
✦ Definition~90s read
What is React Node OpenSSL Error Fix?

The React OpenSSL error (ERR_OSSL_EVP_UNSUPPORTED, error:0308010C) is a build-time crash that hits Create React App and webpack 4 projects on Node 17 and newer. Node 17 replaced OpenSSL 1.1.1 with OpenSSL 3.0, which disables legacy hash algorithms like md4 unless explicitly enabled.

Think of it like a new building inspector arriving in town.

Webpack 4 — bundled inside react-scripts 4 — calls crypto.createHash('md4') on every build to fingerprint modules. OpenSSL 3 refuses, Node throws, and the build dies before a single component compiles. Your application code is never involved; the failure sits entirely in the toolchain layer between Node's crypto module and webpack's default hashFunction.

The fix landscape has three tiers. The instant bridge is NODE_OPTIONS=--openssl-legacy-provider, which reloads the old algorithms and unblocks builds in under a minute. The scoped cure is upgrading react-scripts to 5+, which brings webpack 5 and its xxhash64 default — no blocked algorithms, no flags.

The structural escape is migrating to Vite, which replaces webpack with esbuild and Rollup and makes the error class impossible. Around all three sits version hygiene: pinning Node with .nvmrc so local, team, and CI builds agree, and never downgrading to end-of-life Node 16 as a strategy.

Plain-English First

Think of it like a new building inspector arriving in town. Your apartment (your React code) is perfectly fine, but the old elevator (webpack 4) uses a part the new inspector has banned. The inspector shuts the whole building down — not because your apartment is unsafe, but because the elevator uses the banned part. The quick fix is a temporary permit for the old elevator. The real fix is replacing the elevator with a modern one that uses approved parts.

⚙ Browser compatibility
Latest versions — ✓ supported
ChromeFirefoxSafariEdge

You bump Node from 16 to 18, run npm start, and instead of your app you get a wall of red ending in error:0308010C:digital envelope routines::unsupported. Nothing in your code changed. That's what makes this one so maddening.

Here's the short version. Node 17 swapped in OpenSSL 3, which blocks the md4 hash your old webpack uses on every build. Your components are innocent — the toolchain is guilty.

The unblock takes sixty seconds. The permanent cure takes an afternoon. You'll get both here, plus the upgrade path that stops this from ever waking you up again.

Why Node 17 Broke Every Old React Build Overnight

Every React developer meets this error the same way: a Node upgrade, then a red screen of death mentioning digital envelope routines. The message looks like a crypto catastrophe in your code. It isn't. Your components never touch md4 — your bundler does.

Webpack 4 uses md4 as its default hashFunction for module identifiers. Each rebuild hashes every module so the cache knows what changed. That design dates from an era when OpenSSL let any algorithm through without complaint.

Node 17 replaced OpenSSL 1.1.1 with OpenSSL 3.0. Version 3 sorts algorithms into providers, and the old ones — md4 included — sit in a legacy provider that stays off unless you ask for it. Webpack 4 doesn't ask. It just calls crypto.createHash('md4') and falls over when OpenSSL says no.

📊 Production Insight
This exact collision froze 14 deploys for one team on a Friday afternoon. The base image bumped Node 16 to 18, every fresh build died in under 30 seconds, and staging stayed green only because Docker layer caching reused a stale Node 16 layer. Rule: force a no-cache staging build after any base-image change.
🎯 Key Takeaway
Your code is innocent. Webpack 4 calls md4, OpenSSL 3 blocks md4 by default, and Node 17+ is where those two facts collide.

The 60-Second Unblock: NODE_OPTIONS Legacy Provider

When the deploy queue is frozen, you want the sixty-second fix first. Setting NODE_OPTIONS=--openssl-legacy-provider tells Node to load OpenSSL's legacy provider at startup, which re-allows md4. Webpack 4's hash call succeeds and the build proceeds exactly as it did on Node 16.

The cleanest way to set it is inside package.json so every environment inherits it. Install cross-env once, then prefix your scripts. That single change fixes local dev, CI, and Docker builds simultaneously because they all run the same script.

Verify with a fresh build, then check node -p process.versions.openssl to confirm you're on 3.x with the provider loaded. If the build passes, you're unblocked — now schedule the permanent fix before this flag becomes invisible load-bearing infrastructure.

package.jsonJSON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// package.json — one change, every environment fixed
{
  "scripts": {
    "start": "cross-env NODE_OPTIONS=--openssl-legacy-provider react-scripts start",
    "build": "cross-env NODE_OPTIONS=--openssl-legacy-provider react-scripts build"
  },
  "devDependencies": {
    "cross-env": "^7.0.3"
  }
}

// verify the diagnosis first:
// node -p process.versions.openssl   -> 3.x means OpenSSL 3 is active
// npm ls webpack                      -> 4.x under react-scripts confirms it
⚠ The Legacy Flag Is a Bridge, Not a Home
The flag re-enables retired crypto algorithms process-wide. It unblocks your build tonight, but it also weakens the default security posture of that Node process. Ship it as a bridge while the real upgrade is scheduled — not as a permanent resident of your Dockerfile.
📊 Production Insight
One team exported the flag only in the lead dev's .bashrc. Local builds passed, CI failed for a week, and Docker failed differently. Moving the flag into package.json scripts fixed all three environments in a single commit. Rule: if the fix isn't in git, it isn't a fix.
🎯 Key Takeaway
Put the flag in package.json scripts via cross-env so local, CI, and Docker all inherit the unblock from one version-controlled source.

The Permanent CRA Fix: Upgrade react-scripts to 5+

The legacy flag buys time. The react-scripts 5 upgrade spends it well. Version 5 bundles webpack 5, and webpack 5 replaced md4 with xxhash64 as the default hash function. xxhash64 lives outside the blocked set, so OpenSSL 3 never objects and the error class vanishes.

Budget one to two hours. Bump react-scripts, delete node_modules and package-lock.json, reinstall, and run the full test suite. Most breaking changes between 4 and 5 involve polyfills for Node core modules and stricter ESLint rules — both fixable by following the migration warnings one at a time.

As a bonus, webpack 5's persistent caching usually trims rebuild times noticeably. Teams commonly report 20-40% faster incremental builds after the upgrade, so this fix pays for its own migration effort within weeks.

📊 Production Insight
A team that postponed this upgrade for a year accumulated five scripts depending on the legacy flag. When Node 22 changed flag handling, all five broke at once. The team that upgraded to react-scripts 5 within the sprint never thought about OpenSSL again. Rule: schedule the upgrade in the same ticket as the flag.
🎯 Key Takeaway
react-scripts 5 brings webpack 5, webpack 5 uses xxhash64 instead of md4, and the OpenSSL conflict disappears with no flags.

The Escape Hatch: Migrating from CRA to Vite

If you're touching the build anyway, consider leaving webpack behind. Vite compiles with esbuild and bundles with Rollup — neither calls the blocked hash — so the OpenSSL error is structurally impossible. You also get near-instant dev startup because Vite serves native ES modules instead of bundling first.

Migration for a typical CRA app takes half a day: install Vite and the React plugin, move index.html to the root, convertcats environment variables from REACT_APP_ to VITE_, and swap the scripts. The Vite docs cover each step, and most apps need no component changes at all.

Cold builds commonly drop 50-80% after the move. For a 3-minute CRA build, that's a sub-minute Vite build — a difference your CI bill will notice within a month.

vite.config.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
// vite.config.js — minimal CRA replacement
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  server: { port: 3000 },
  build: { outDir: 'build' }, // keep CRA's output dir for existing deploy scripts
});

// index.html moves to project root; change:
//   <div id="root"></div> + <script src="/src/index.jsx">
// env vars: REACT_APP_API_URL -> VITE_API_URL, read via import.meta.env.VITE_API_URL
Try it live
📊 Production Insight
One startup migrated during a hack afternoon and watched CI build minutes fall from 190 to 45 per week. The OpenSSL fix was a side effect; the build-speed win was the headline in the retro. Rule: if your CRA app is due any build overhaul, do the Vite move instead of patching webpack 4.
🎯 Key Takeaway
Vite never calls md4, so the error class is gone by construction — plus cold builds typically drop 50-80%.

Pinning Node So This Never Surprises You Again

Version drift is how this error keeps resurrecting. Your laptop runs Node 20, your teammate runs Node 18, CI runs whatever the base image shipped last quarter — and each combination behaves differently around OpenSSL. The cure is boring: one pinned version everywhere.

Drop an .nvmrc file with the exact version (20.11.0, not just 20) in the repo root. Add engines to package.json as a backstop. Make CI read .nvmrc instead of hardcoding its own image tag, and pin the Dockerfile base to the same minor.

From then on, a Node bump is a deliberate pull request that updates three files and runs the full suite — never a surprise that pages someone on a Friday.

.nvmrcBASH
1
2
3
4
5
6
7
8
9
10
11
# .nvmrc — exact version, committed to git
20.11.0

# package.json — backstop so npm warns on mismatch
# { "engines": { "node": "20.11.0", "npm": ">=10" } }

# Dockerfile — same minor, no drift
# FROM node:20.11.0-alpine

# verify all three agree:
node -v && cat .nvmrc && grep -A2 '"engines"' package.json
📊 Production Insight
After pinning Node 20.11.0 in .nvmrc, Dockerfile, and CI, one team caught the next OpenSSL-adjacent breakage in a pull request instead of production — the suite failed on the bump branch, the upgrade was sequenced properly, and zero deploys froze. Rule: the version file is the contract; CI must read it, not duplicate it.
🎯 Key Takeaway
.nvmrc plus engines plus a matching Dockerfile pin turns Node bumps from Friday surprises into deliberate, tested pull requests.

Choosing Your Fix: Speed Versus Permanence

Not every fix deserves equal trust. The legacy flag is instant but temporary. The react-scripts upgrade is permanent but scoped to CRA. Vite is permanent plus faster but costs half a day. Pinning Node 16 is quick and wrong — it's end-of-life with no security patches.

And one option deserves explicit rejection: hand-patching webpack's hashFunction to sha256 inside node_modules or via config overrides. It silences the error until the next npm install wipes the patch, then fails at the worst moment with no record of what changed.

Choose in this order: flag tonight to unblock, pin Node this week to stabilize, upgrade react-scripts or move to Vite this sprint to cure. Each step makes the next one safer.

📊 Production Insight
Teams that stop at the flag average a repeat incident within 6 months when Node bumps again. Teams that complete the upgrade path close the error class for good. The difference isn't skill — it's whether the cure got a sprint ticket or just a chat message.
🎯 Key Takeaway
Flag tonight, pin this week, upgrade this sprint — and never hand-patch webpack internals that vanish on reinstall.
● Production incidentPOST-MORTEMseverity: high

The Friday Node Upgrade That Froze 14 Deploys for 3 Hours

Symptom
Every npm start and every CI build crashed in under 30 seconds with error:0308010C:digital envelope routines::unsupported. The app rendered nothing. Rollback to the previous container image restored service, but any fresh build from the new image failed identically.
Assumption
The team assumed a Node minor bump was safe because no application code changed and the staging deploy had passed the week before. Nobody realized staging's Docker cache had reused an old layer still built on Node 16, so staging never actually compiled anything with the new OpenSSL.
Root cause
Node 18 ships OpenSSL 3.0, where the md4 hash is disabled by default. The app still used react-scripts 4, which bundles webpack 4 — and webpack 4 calls crypto.createHash('md4') on every build. OpenSSL 3 refused the call, Node raised ERR_OSSL_EVP_UNSUPPORTED, and all 14 pipeline builds failed within 20 minutes of the base-image bump.
Fix
Two changes shipped the same day. First, NODE_OPTIONS=--openssl-legacy-provider was added to the build scripts via cross-env so every environment — local, CI, Docker — got the unblock from one source. Second, the team pinned Node 20.11.0 in .nvmrc and the Dockerfile, then scheduled the react-scripts 4 to 5 upgrade for the next sprint, which removed the flag entirely when webpack 5 replaced md4 with xxhash64.
Key lesson
  • A runtime minor bump can break the build toolchain without touching your code — pin Node per project with .nvmrc and keep CI on the same version.
  • Docker layer caching can hide a broken build on staging while production burns; force a no-cache build on staging after any base-image change.
  • Temporary flags belong in version-controlled build scripts, not in one engineer's shell profile — if the fix isn't in git, it isn't a fix.
Production debug guideFour ways this error shows up in real projects — and the exact check that nails each one.4 entries
Symptom · 01
npm start crashes immediately with error:0308010C or ERR_OSSL_EVP_UNSUPPORTED
Fix
Scroll to the top of the stack trace and look for crypto.createHash or error:0308010C. If either appears, this is the OpenSSL 3 vs md4 conflict — not a syntax error in your code. Confirm with node -p process.versions.openssl; a 3.x result seals the diagnosis.
Symptom · 02
Build passes on your laptop but fails in CI with the same error
Fix
Run node -v locally and compare against the CI image tag and the Dockerfile base. Add an .nvmrc with the working version, then make CI read it (nvm use in the pipeline or an .nvmrc-aware action). Re-run the pipeline and confirm green.
Symptom · 03
You set the legacy flag but the error persists, or new loader errors appeared
Fix
Run npm ls webpack to see which copy react-scripts actually resolves. If you see nested webpack 4 under react-scripts while a webpack 5 sits at top level, remove your manual upgrade, bump react-scripts itself to 5+, wipe node_modules and the lockfile, and reinstall clean.
Symptom · 04
Docker build fails even though local builds work with the flag
Fix
Check where NODE_OPTIONS is defined: grep -r openssl-legacy-provider Dockerfile docker-compose .github/ Jenkinsfile .env*. If it only exists in one place, add it to the others. Better still, move it into package.json scripts with cross-env so every environment inherits it from one source.
React OpenSSL Error — Every Fix Compared at a Glance
FixEffortDurabilityBest for
NODE_OPTIONS legacy flag1 minuteTemporary bridgeUnblocking a deploy tonight
Upgrade react-scripts to 5+1-2 hoursPermanent for CRA appsTeams staying on Create React App
Migrate to ViteHalf a dayPermanent + faster buildsTeams planning any build overhaul
Pin Node 16 via nvm/.nvmrc10 minutesFragile, EOL runtimeLocal debugging only, never production
Patch webpack hashFunction30 minutesBreaks on reinstallNobody — avoid this hack
⚙ Quick Reference
3 commands from this guide
FileCommand / CodePurpose
package.json{The 60-Second Unblock
vite.config.jsexport default defineConfig({The Escape Hatch
.nvmrc20.11.0Pinning Node So This Never Surprises You Again

Key takeaways

1
Node 17+ ships OpenSSL 3, which blocks the md4 hash webpack 4 calls on every React build.
2
NODE_OPTIONS=--openssl-legacy-provider unblocks builds in a minute
use it as a bridge, not a home.
3
Upgrading react-scripts to 5+ (webpack 5, xxhash64) removes the root cause permanently.
4
Pin Node per project with .nvmrc so local, team, and CI builds all agree on one version.
5
Migrating to Vite sidesteps webpack 4 entirely and typically cuts cold builds by 50-80%.

Common mistakes to avoid

4 patterns
×

Exporting NODE_OPTIONS=--openssl-legacy-provider globally in .bashrc

Symptom
Every Node project on the machine silently uses the legacy provider, and a newer app that depends on OpenSSL 3 behavior starts throwing unrelated crypto errors months later.
Fix
Set NODE_OPTIONS only for the old project, not globally. Add it to that project's .env file or its start script: "start": "react-scripts start" becomes "cross-env NODE_OPTIONS=--openssl-legacy-provider react-scripts start". Keep other projects on the default provider.
×

Assuming the whole team runs the same Node version

Symptom
The fix works on your laptop but CI fails with ERR_OSSL_EVP_UNSUPPORTED because the pipeline image still uses Node 17 while you run Node 20.
Fix
Pin the Node version per project with an .nvmrc file (e.g. 20.11.0) and document the required version in the README. CI should read .nvmrc so local and pipeline builds match exactly.
×

Keeping --openssl-legacy-provider forever instead of upgrading

Symptom
A year later the legacy flag is load-bearing in five deploy scripts, nobody remembers why, and a Node 22 upgrade breaks the build again because the flag's behavior changed.
Fix
Treat the legacy provider as a bridge, not a destination. Schedule the upgrade: bump react-scripts to 5+ (webpack 5 uses xxhash64 instead of md4) or migrate to Vite. Track it as tech debt with a deadline.
×

Force-upgrading webpack inside react-scripts 4 manually

Symptom
Two webpack versions end up nested in node_modules, the build picks the old one, and the OpenSSL error persists alongside brand-new loader resolution errors.
Fix
Run npm ls webpack and check react-scripts' dependency tree before upgrading webpack by hand. Let react-scripts 5 bring its own webpack 5. If you must upgrade manually, delete node_modules and package-lock.json, then reinstall clean.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
Why does a React build fail with ERR_OSSL_EVP_UNSUPPORTED on Node 17+?
Q02SENIOR
A production deploy is blocked by this error. What is your short-term an...
Q03JUNIOR
Why was md4 involved in a frontend build at all?
Q01 of 03SENIOR

Why does a React build fail with ERR_OSSL_EVP_UNSUPPORTED on Node 17+?

ANSWER
Node 17 bundled OpenSSL 3.0, which moved old algorithms like md4 into a legacy provider that is off by default. Webpack 4 (used by react-scripts 4) hashes modules with md4 via crypto.createHash('md4'). OpenSSL 3 refuses, Node surfaces error:0308010C, and the build dies. The flag --openssl-legacy-provider re-enables those algorithms as a workaround.
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
What does ERR_OSSL_EVP_UNSUPPORTED actually mean?
02
Is NODE_OPTIONS=--openssl-legacy-provider safe?
03
Why does upgrading react-scripts to 5 fix it?
04
Can I just downgrade to Node 16?
05
Does migrating to Vite avoid this error completely?
N
Naren Founder & Principal Engineer

20+ years shipping production JavaScript and front-end systems at scale. Lessons pulled from things that broke in production.

Follow
Verified
production tested
September 22, 2026
last updated
1,799
articles · all by Naren
🔥

That's React.js. Mark it forged?

3 min read · try the examples if you haven't

Previous
TypeScript vs JavaScript
48 / 48 · React.js
Next
Node Semver Tilde vs Caret Ranges