ERR_OSSL_EVP_UNSUPPORTED는 노드 17 이후 OpenSSL 3이 MD4를 막아 웹팩 빌드 해시가 죽을 때 나는 코드입니다. 당장은 NODE_OPTIONS에 openssl-legacy-provider를 넣고, 남기려면 hashFunction을 sha256으로 바꾸거나 웹팩 5로 올립니다. dlopen ABI 실패와는 다른 줄입니다. Node.js, webpack, CI, 한국 맥 개발. 2026년 10월 문서.
ERR_OSSL_EVP_UNSUPPORTED는 키가 틀린 신호가 아니라, 노드 17 이후 오픈에스에스엘 3이 옛 해시를 막아 웹팩 빌드가 멈춘 오류입니다. 스택이 해시 생성이면 해시 함수를 바꾸거나 빌드 명령에만 옛 제공자를 붙이세요.
맥에서 노드를 스물두 번대로 올린 뒤 빌드만 디지털 봉투 오류에서 멈추는 장면이 이 줄입니다. 개발 서버는 조용한데 배포 빌드만 죽는 경우도 같습니다.
네이티브 애드온이 안 열리는 경우와는 원인이 다릅니다. 근거는 Node.js CLI 문서와 웹팩 해시 함수 문서에 뒀습니다. 셸 설정에 영구히 넣기 전에, 어느 패키지가 옛 해시를 부르는지 먼저 보세요.
이 코드는 해시가 막힌 줄이다
노드 17부터 런타임 OpenSSL이 3입니다. 3은 MD4처럼 쓰임이 줄어든 알고리즘을 기본 제공자에서 빼 두었습니다.
옛 웹팩은 모듈 해시를 만들 때 crypto.createHash('md4')를 부릅니다. OpenSSL 3이 그 알고리즘을 거절하면 노드가 ERR_OSSL_EVP_UNSUPPORTED를 던지고, 메시지에는 digital envelope routines::unsupported가 붙습니다. 라이브러리 이름이 암호 봉투라서 인증서 문제로 보이지만, 빌드 해시 한 줄인 경우가 많습니다.
노드 17.0.0 릴리스 노트도 같은 코드를 적습니다. 앱이나 모듈이 OpenSSL 3이 기본으로 허용하지 않는 알고리즘을 쓰면 이 오류가 나고, 임시 우회로 --openssl-legacy-provider를 넣었다고 적혀 있습니다. 16.17.0에도 그 플래그가 이식됐습니다. 16 초기는 플래그가 없을 수 있으니, 우회를 쓰기 전에 node -p process.versions.openssl로 3대인지 확인하세요.
먼저 볼 스택 | webpack/lib/util/createHash.js나 NormalModule._initBuildHash가 보이면 해시입니다. process.dlopen이나 .node가 보이면 네이티브 모듈이라 이 글의 우회를 쓰면 안 됩니다.
OpenSSL 3이 MD4를 막으면 웹팩 해시 단계에서 빌드가 멈춘다
아직 MD4를 부르는 도구
넥스트 12 이후 기본 웹팩 5는 자체 MD4 구현을 갖고 있어, 요즘 넥스트만 쓰면 이 줄이 잘 안 납니다. 남는 자리는 옛 도구입니다.
도구
왜 죽나
먼저 볼 파일
웹팩 4, 5.61 이전
기본 해시가 MD4
node_modules/webpack/package.json 버전
react-scripts 4
안에 웹팩 4가 숨음
package.json의 react-scripts
laravel-mix 옛 메이저
웹팩 4 래퍼
webpack.mix.js
직접 createHash('md4')
플러그인이 OpenSSL에 의존
스택의 패키지 이름
웹팩 5.61.0부터는 MD4를 웹어셈블리로 따로 넣어, OpenSSL에 MD4를 물어보지 않습니다. npm ls webpack에서 5.61 미만이 트리 안에 있으면 그 복사본이 빌드를 죽입니다. 앱의 직접 의존이 5대여도, 옛 스토리북이나 크레이가 4대를 끌어오면 그 명령만 죽습니다.
로컬 노드 18과 액션 노드 22가 둘 다 OpenSSL 3이면 둘 다 죽어야 정상입니다. 한쪽만 살면 노드 메이저가 다른 게 아니라, 그 잡은 다른 스크립트를 돌리는 경우가 많습니다. node -v를 빌드 스텝 첫 줄에 찍으세요.
OpenSSL 버전과 웹팩 복사본 확인
node -p "process.version + ' openssl ' + process.versions.openssl"
npm ls webpack --all
# 스택에 createHash가 있으면 해시 문제다
# 스택에 dlopen, .node가 있으면 ABI 문제다
레거시 프로바이더는 빌드에만 켠다
당장 배포를 살려야 하면 OpenSSL 3의 레거시 제공자를 그 프로세스에만 켭니다. 노드 문서의 --openssl-legacy-provider가 그 스위치입니다.
맥과 우분투 액션은 스크립트 앞에 환경 변수를 붙입니다. NODE_OPTIONS는 노드가 기동 플래그로 읽습니다. 윈도우 cmd는 set NODE_OPTIONS=--openssl-legacy-provider && npm run build, 파워셸은 $env:NODE_OPTIONS로 같은 값을 줍니다.
zsh 프로필이나 액션의 잡 전체 env에 넣으면, 넥스트 서버와 크론까지 레거시 알고리즘이 다시 열립니다. 빌드 스크립트 한 줄이 범위입니다. 플래그는 노드 17과 16.17 이상에서만 있습니다. 더 옛 노드에 붙이면 노드가 알 수 없는 옵션으로 바로 죽습니다.
설정이 내 저장소에 있으면 hashFunction을 바꾸고, 없으면 빌드 스크립트에만 플래그를 둔다
dlopen, 없는 버전과 줄을 가른다
노드만 올리면 죽는 오류가 세 줄은 됩니다. 메시지가 다르면 고치는 자리도 다릅니다.
ERR_DLOPEN_FAILED와 NODE_MODULE_VERSION은 .node 파일의 ABI가 지금 노드와 다를 때입니다. npm rebuild나 그 패키지의 재설치가 맞고, OpenSSL 플래그는 그 파일을 다시 컴파일하지 않습니다. ETARGET는 레지스트리에 그 버전 번호가 없을 때라, 설치 단계에서 이미 끝납니다. 빌드 스크립트 한가운데 digital envelope가 보이면 설치는 끝난 뒤입니다.
프리즈마 쿼리 엔진이 alpine에서 openssl 공유 라이브러리를 못 찾는 줄도 따로입니다. 그건 엔진 바이너리와 베이스 이미지의 libssl 조합이고, 웹팩 해시 스택이 없습니다. 스택 첫 프레임의 파일 경로가 어느 글인지 가릅니다.
한국에서 맥 한 대로 프리뷰를 올리는 경우에는 터미널과 편집기가 서로 다른 노드를 부르는 일이 잦습니다. 홈브루로 깐 바이너리와 버전 관리자가 깐 바이너리가 같이 있으면, 통합 터미널만 옛 메이저에 남아 빌드가 조용히 통과합니다. 어느 경로의 노드인지 먼저 찍고, 둘의 메이저를 같게 맞춘 다음 빌드를 다시 돌리세요. 경로가 다르면 플래그를 넣어도 편집기 쪽 빌드는 그대로 죽습니다.
깃허브 액션 로그를 볼 때도 같습니다. 잡이 시작된 직후 버전을 한 줄 출력해 두면, 로컬과 러너의 메이저가 어긋난 것인지 해시 문제인지가 바로 갈립니다. 메이저가 같으면 임시 스위치는 그 빌드 스텝에만 두고, 이어서 해시 함수를 바꾸는 커밋을 올리세요. 사용자 셸 설정에 스위치를 박아 두면, 카카오 로그인 콜백을 로컬에서 재현하는 서버까지 옛 암호 알고리즘이 다시 열립니다. 빌드가 만든 산출물만 배포하면 실행 중인 서버를 그 스위치로 재시작할 이유는 없습니다.
의존을 올릴 시간이 없는 주에는 빌드 스크립트 한 줄로 배포를 살리고, 같은 주에 웹팩 복사본을 찾는 커밋을 분리하세요. 한 커밋에 노드 버전, 해시 설정, 무관한 리팩터를 섞으면 다음에 누가 플래그를 지워도 이유가 안 남습니다. 커밋 메시지에는 옛 해시가 막혀 빌드 명령에만 우회를 넣었다는 문장을 적으면, 나중에 의존을 올린 뒤 그 줄을 빼기 쉽습니다.
[ ] node -v 와 process.versions.openssl 이 3대인지 봤다
[ ] 스택이 createHash 인지 dlopen 인지 갈랐다
[ ] npm ls webpack 으로 5.61 미만 복사본을 찾았다
[ ] 레거시 프로바이더는 빌드 스크립트에만 붙였다
[ ] 설정이 있으면 hashFunction 을 sha256 으로 바꿨다
digital envelope는 해시, dlopen은 네이티브 파일, ETARGET는 설치 목록이다