조회수: 19

UNABLE_TO_VERIFY_LEAF_SIGNATURE (Node.js) 해결

UNABLE_TO_VERIFY_LEAF_SIGNATURE는 Node가 신뢰 루트에 닿지 못한 것입니다. 서버 중간 인증서·NODE_EXTRA_CA_CERTS·프록시를 순서대로 점검합니다. 무료 즉시 진단으로 바로 확인.

내 도메인에 이 문제가 있는지 지금 확인

무료, 가입 불필요. 이 가이드가 다루는 항목을 바로 검사하고 조치 방법을 알려드립니다.

문제

Node.js가 Error: unable to verify the first certificatecode: 'UNABLE_TO_VERIFY_LEAF_SIGNATURE'로 연결을 거부합니다. Node는 서버 인증서를 받았지만, 그것으로부터 자신이 신뢰하는 인증기관까지 이어지는 체인을 세우지 못했습니다. 거의 항상 이건 특정 고리 하나 — 서버 리프를 공개 루트로 잇는 중간 인증서 — 가 빠졌다는 뜻이고, 그 고리가 빠진 곳은 둘 중 하나입니다: 서버(중간 인증서 없이 리프만 보냄) 또는 내 컴퓨터(Node 신뢰 저장소에 루트가 없거나, 프록시가 Node에 없는 CA로 트래픽을 재서명함). 인증서 자체는 대개 유효합니다. 어느 쪽이 고리를 빠뜨렸는지 가려내는 게 과제의 전부입니다.

증상

  • 오류 객체에 code: 'UNABLE_TO_VERIFY_LEAF_SIGNATURE'가 찍히고, 메시지는 흔히 unable to verify the first certificate. npm install은 레지스트리나 프록시가 Node가 완성 못 하는 체인을 보낼 때 같은 걸 npm ERR! code UNABLE_TO_VERIFY_LEAF_SIGNATURE로 드러냅니다.
  • 형제 코드는 서로 다른 문제를 가리킵니다: SELF_SIGNED_CERT_IN_CHAIN(경로에 사설·가로채기 CA), DEPTH_ZERO_SELF_SIGNED_CERT(엔드포인트가 자체 서명 인증서 제시), CERT_HAS_EXPIRED(체인 속 인증서가 유효기간 초과), ERR_TLS_CERT_ALTNAME_INVALID(인증서는 신뢰되지만 다른 호스트명으로 발급). 각각 수정법이 다릅니다.
  • 한 호스트만 실패하고 나머지는 정상이면: 문제는 그 호스트의 체인 — 거의 항상 서버의 중간 인증서 누락.
  • 모든 HTTPS 호스트에서 실패하거나, 사내 네트워크 안에서만 실패한다면: 문제는 내 컴퓨터의 Node 신뢰 저장소 — Node가 못 보는 프록시 루트.

주요 원인 3가지

  1. 서버가 중간 인증서를 안 보냄 - 압도적으로 가장 흔한 원인. 서버가 리프만 제시하고 신뢰 루트로 잇는 중간 인증서를 뺍니다. 브라우저는 중간 인증서를 스스로 받아와(AIA fetching) 가려주지만 Node는 안 합니다. 그래서 체인을 완성하지 못하고 UNABLE_TO_VERIFY_LEAF_SIGNATURE를 던집니다. 수정은 서버에서 — 리프만이 아니라 풀체인(리프 + 중간)을 배포하세요. 바깥에서 증명할 수 있는 원인이니 이걸 가장 먼저 확인하세요.
  2. 프록시나 백신이 TLS를 가로챔 - 사내 미들박스와 “HTTPS 검사” 백신은 아웃바운드 트래픽을 복호화한 뒤 자기 사설 CA로 재서명합니다. 그 CA는 관리 대상 기기의 OS 저장소에 있지만, Node는 OS 저장소가 아니라 자체 번들 Mozilla 루트로 검증하므로 찾을 수 없는 발급자로 보입니다. 보통 SELF_SIGNED_CERT_IN_CHAIN으로 나타나고, 그래서 사무실 네트워크나 VPN에서만 실패가 뜨는 경우가 많습니다.
  3. Node 신뢰 저장소에 정당하게 필요한 루트가 없음 - 가로채기 말고도, Node가 필요로 하는 커스텀·내부 루트 — 사설 PKI, 내부 레지스트리, 어플라이언스 — 가 번들 Mozilla 저장소에 없습니다. Node는 알려주기 전엔 그 존재를 모르므로, 설정이 환경에 맞게 올바른데도 리프가 검증 불가로 보입니다.

DechoNet으로 진단

  • SSL 검사는 서버가 실제로 회선에 실어 보내는 체인을 그대로 보여줍니다 — 중간 인증서가 있는지, 서버가 리프만 보내는지. 여기서 체인이 불완전하면 원인 #1이 확정이고 수정은 서버에서 합니다. 공개 인터넷에서는 체인이 완전한데 내 컴퓨터의 Node만 실패한다면 문제는 로컬입니다 — 트래픽을 재서명하는 프록시나 누락된 내부 루트(원인 #2 또는 #3).
  • HTTP 검사는 인증서 문제를 제쳐두고 엔드포인트가 실제로 응답하는지 확인해줘서, 함께 죽어 있는 호스트를 두고 신뢰 오류를 쫓지 않게 해줍니다.

해결 체크리스트

  • 범위부터 읽으세요. 한 호스트만 실패하고 나머지는 정상 → 서버 체인 문제. 모든 호스트 실패, 또는 사내 네트워크에서만 → Node 신뢰 저장소. 이 한 질문이 수정 전체의 방향을 정합니다.
  • 한 호스트가 실패하면 서버가 보내는 체인을 보세요: openssl s_client -connect YOUR_DOMAIN:443 -servername YOUR_DOMAIN -showcerts. CERTIFICATE 블록이 하나만 보이면 중간 인증서가 빠진 것 — 서버에서 인증서를 풀체인 번들로 다시 설치하고 SSL 검사로 중간 인증서가 이제 나타나는지 확인하세요. 코드가 아니라 서버에 있더라도 이게 올바른 수정입니다.
  • 정당한 추가 루트(사내 프록시, 내부 CA)라면 아무것도 끄지 말고 루트만 추가하세요: NODE_EXTRA_CA_CERTS=/path/to/extra-ca.pem node app.js. 파일은 하나 이상의 PEM 인증서이고, Node는 이 변수를 프로세스 시작 시에만 읽습니다 — Node를 켜기 전에 설정하고, 바꾼 뒤엔 재시작하세요. 안 그러면 조용히 아무 효과가 없습니다.
  • 개별 요청에 땜질하기보다 신뢰를 고치는 걸 우선하세요. 신뢰를 한 클라이언트로 좁혀야 한다면 전역 스위치 대신 CA를 명시적으로 넘기세요: https.request({ ca: fs.readFileSync('ca.pem') }).
  • NODE_TLS_REJECT_UNAUTHORIZED=0을 설정하지 마세요. 프로세스 전체와 그것이 대화하는 모든 호스트에 대한 검증을 끕니다 — Node가 정확히 그렇게 경고를 찍습니다. 막힌 걸 뚫으려 썼다면 지우고, 대신 특정 루트를 추가하세요.
  • 코드가 실제로 CERT_HAS_EXPIRED라면 신뢰가 아니라 날짜를 고치세요: 서버 인증서 만료일(SSL 검사가 보여줌)과 클라이언트 기기의 시계를 확인 — 시스템 시계가 틀리면 유효한 인증서도 만료된 것처럼 보입니다.

언제 에스컬레이션할까

  • SSL 검사가 공개 인터넷에서 완전하고 올바른 체인을 보여주는데도 특정 한 기기·네트워크에서만 Node가 실패한다면, 재서명하는 CA는 그 네트워크에 있습니다 — 프록시나 백신. 신뢰할 올바른 루트를 배포하는 건 엔드포인트/IT 결정이지 앱별 플래그가 아닙니다.
  • 엔드포인트가 정말로 사설·자체 서명 CA를 쓴다면(사내 서비스, 어플라이언스, 개발 환경) NODE_EXTRA_CA_CERTSca 옵션으로 그 특정 CA만 신뢰하세요 — 검증을 전역으로 끄지 마세요. 배포되는 모든 NODE_TLS_REJECT_UNAUTHORIZED=0은, 돌아가는 내내 조용히, 공격자의 인증서를 내 서버의 것만큼 선뜻 받아들이는 프로세스입니다.

관련 도구

관련 가이드

가이드 공유

[Ad] Guide Detail Inline
← 전체 가이드 보기