/개발/PKIX path building failed 해결법: keytool cacerts import 5분 가이드
개발PKIX path building failedSunCertPathBuilderException

PKIX path building failed 해결법: keytool cacerts import 5분 가이드

Java의 'PKIX path building failed' (SunCertPathBuilderException) 에러를 5가지 원인으로 진단하고 openssl 추출과 keytool cacerts import, Maven·Gradle·Docker 우회까지 복붙 명령으로 5분 안에 해결합니다.

PKIX path building failed 해결법: keytool cacerts import 5분 가이드

PKIX path building failed 5분 진단·해결 (keytool cacerts import)

빌드 로그에 갑자기 이 한 줄이 뜨면서 배포가 멈춘 적 있으신가요?

CODE
javax.net.ssl.SSLHandshakeException: PKIX path building failed:
sun.security.provider.certpath.SunCertPathBuilderException:
unable to find valid certification path to requested target

분명 어제까지 잘 돌던 코드인데 오늘 빌드가 깨졌다면, 이건 코드 버그가 아닙니다. JVM이 상대 서버의 인증서를 "신뢰할 수 없다"고 판단한 것, 즉 트러스트스토어(cacerts) 신뢰 문제입니다. 핵심 포인트 하나만 기억하세요. curl이나 pip는 운영체제(OS)의 인증서 저장소를 쓰지만, Java는 JVM 전용 저장소인 cacerts만 본다는 점입니다. 그래서 "브라우저·curl은 되는데 Java만 실패"하는 일이 자주 생깁니다. OS에 사내 CA가 깔려 있어도 JVM은 모르는 거죠.

이 글은 "지금 막힌 사람"이 위에서부터 따라 하면 풀리도록 진단 → 명령 → 검증 순서로 구성했습니다.

5분 진단: 같은 에러, 5가지 원인 분기표

메시지는 똑같아도 원인은 크게 5가지로 갈립니다. 먼저 판별 신호로 내 상황을 좁혀보세요.

#원인판별 신호1차 조치
사설 CA / 자체서명 인증서사내 시스템·내부 API에서만 발생, 공인 사이트는 정상사설/루트 CA를 cacerts에 import
중간(Intermediate) 인증서 누락브라우저는 OK인데 Java만 실패, s_client에서 체인이 1개만 나옴중간 인증서까지 import
사내 프록시 MITM (Zscaler·Netskope)회사망에서만 실패, 인증서 발급자가 사내 보안솔루션 이름프록시 루트 CA를 import
JDK cacerts 미갱신·구버전 번들오래된 JDK8, 신규 발급기관(예: ISRG/Let's Encrypt) 누락JDK 업데이트 또는 루트 CA import
인증서 자체 만료s_clientnotAfter 날짜가 지남서버측 인증서 갱신 요청

판별의 결정적 도구는 openssl s_client입니다. 실제 어떤 체인이 내려오는지 두 눈으로 확인하세요.

Bash
openssl s_client -connect api.example.com:443 -showcerts < /dev/null

출력에서 Certificate chain 아래에 인증서가 루트 → 중간 → 서버까지 모두 나오는지, verify return code가 무엇인지 봅니다. 체인이 짧으면 ②번, 발급자(issuer)가 낯선 사내 이름이면 ③번이 유력합니다.

표준 해결: openssl 추출 → keytool로 cacerts에 import

가장 정석적인 복구 절차입니다. 막힌 인증서를 추출해 JVM이 신뢰하도록 등록합니다.

1) 인증서 추출 (PEM 저장)

Bash
# 서버가 내려주는 체인을 파일로 저장
openssl s_client -connect api.example.com:443 -showcerts < /dev/null \
  2>/dev/null | openssl x509 -outform PEM > example-ca.crt

여러 인증서가 있다면 -----BEGIN CERTIFICATE----- ~ -----END CERTIFICATE----- 블록을 각각 .crt로 잘라 저장합니다. 루트와 중간 인증서를 모두 등록해야 ②번 케이스가 풀립니다.

2) cacerts에 import

Bash
keytool -importcert \
  -alias example-ca \
  -file example-ca.crt \
  -keystore "$JAVA_HOME/lib/security/cacerts" \
  -storepass changeit \
  -noprompt

3) 등록 확인 (검증)

Bash
keytool -list -alias example-ca \
  -keystore "$JAVA_HOME/lib/security/cacerts" -storepass changeit

경로·비밀번호·권한 주의사항

  • cacerts 경로가 JDK 버전마다 다릅니다.
    • JDK 9 이상: $JAVA_HOME/lib/security/cacerts
    • JDK 8: $JAVA_HOME/jre/lib/security/cacerts
  • OS별 경로 예시
    • macOS: /Library/Java/JavaVirtualMachines/<jdk>/Contents/Home/lib/security/cacerts
    • Linux: /usr/lib/jvm/<jdk>/lib/security/cacerts
    • Windows: C:\Program Files\Java\<jdk>\lib\security\cacerts
  • 기본 비밀번호는 changeit 입니다.
  • 시스템 JDK의 cacerts는 쓰기 권한이 필요합니다. Linux/macOS는 sudo를, Windows는 "관리자 권한 터미널"을 사용하세요.

실무 팁: 저는 LTS 전환(11→17→21) 직후 PKIX 에러를 가장 많이 겪었습니다. JDK를 바꾸면 cacerts도 새 파일이라 예전에 넣었던 사내 CA가 사라지기 때문입니다. "JDK 교체 = cacerts 재등록"을 체크리스트에 박아두면 며칠씩 헤맬 일이 없습니다.

운영 친화적 대안: 커스텀 트러스트스토어 & 빌드 우회

시스템 cacerts를 직접 건드리기 부담스럽다면(운영 서버, 공용 CI 등), 전용 트러스트스토어를 따로 만들어 지정하는 방식이 안전합니다.

Bash
# 별도 트러스트스토어 생성 후 import
keytool -importcert -alias example-ca -file example-ca.crt \
  -keystore custom-truststore.p12 -storetype PKCS12 -storepass mypass -noprompt

애플리케이션 실행 시 JVM 옵션으로 지정합니다.

Bash
java -Djavax.net.ssl.trustStore=/opt/app/custom-truststore.p12 \
     -Djavax.net.ssl.trustStorePassword=mypass \
     -Djavax.net.ssl.trustStoreType=PKCS12 \
     -jar app.jar

Maven 빌드가 깨질 때 (의존성 다운로드에서 실패):

Bash
export MAVEN_OPTS="-Djavax.net.ssl.trustStore=/opt/app/custom-truststore.p12 \
  -Djavax.net.ssl.trustStorePassword=mypass \
  -Djavax.net.ssl.trustStoreType=PKCS12"

프록시 환경이라면 ~/.m2/settings.xml에 프록시도 함께 잡아줍니다.

XML
<settings>
  <proxies>
    <proxy>
      <id>corp</id><active>true</active>
      <protocol>https</protocol>
      <host>proxy.corp.com</host><port>8080</port>
    </proxy>
  </proxies>
</settings>

Gradle 빌드가 깨질 때gradle.properties에 추가:

PROPERTIES
org.gradle.jvmargs=-Djavax.net.ssl.trustStore=/opt/app/custom-truststore.p12 \
  -Djavax.net.ssl.trustStorePassword=mypass \
  -Djavax.net.ssl.trustStoreType=PKCS12

이후 gradle build --refresh-dependencies로 캐시를 무시하고 다시 받습니다.

컨테이너/CI(GitHub Actions·Jenkins) 에서는 base 이미지에 사내 CA가 없어 실패하는 경우가 많습니다. Dockerfile에서 빌드 단계에 CA를 심어주세요.

Dockerfile
FROM eclipse-temurin:21-jdk
COPY example-ca.crt /usr/local/share/ca-certificates/example-ca.crt
RUN keytool -importcert -alias example-ca \
    -file /usr/local/share/ca-certificates/example-ca.crt \
    -keystore "$JAVA_HOME/lib/security/cacerts" \
    -storepass changeit -noprompt

결론: 절대 하지 말 것 + 안전 체크리스트

급하다고 검색하면 나오는 "모든 인증서 신뢰" 코드는 절대 운영에 넣지 마세요.

JAVA
// ⚠️ 안티패턴 — MITM 공격에 그대로 노출됩니다. 사용 금지!
TrustManager[] trustAll = new TrustManager[]{
  new X509TrustManager() {
    public void checkClientTrusted(X509Certificate[] c, String a) {}
    public void checkServerTrusted(X509Certificate[] c, String a) {} // 검증 안 함
    public X509Certificate[] getAcceptedIssuers() { return null; }
  }
};
HttpsURLConnection.setDefaultHostnameVerifier((h, s) -> true); // 위험!

🚨 보안 경고 위 코드와 -Dcom.sun.net.ssl.checkRevocation=false 같은 우회는 인증서 검증 자체를 꺼버립니다. 즉 누군가 중간에서 트래픽을 가로채(MITM) 가짜 서버로 위장해도 그대로 통신합니다. 비밀번호·토큰·DB 자격증명이 평문처럼 노출될 수 있습니다.

안전한 해결 순서 체크리스트

  1. openssl s_client로 실제 체인·발급자·만료일 먼저 확인
  2. ✅ 가능하면 정식 CA(루트·중간)를 정식으로 등록
  3. ✅ 운영/CI는 시스템 cacerts 대신 커스텀 트러스트스토어 사용
  4. ✅ JDK 버전 교체 시 cacerts 재등록 잊지 않기
  5. ✅ 검증 비활성화 코드는 절대 커밋하지 않기

자주 묻는 질문 (FAQ)

Q. keytool 비밀번호가 뭔가요? A. JDK cacerts의 기본 비밀번호는 changeit입니다. 직접 만든 트러스트스토어라면 생성 시 지정한 -storepass 값을 쓰면 됩니다.

Q. 브라우저는 되는데 Java만 안 되는 이유는? A. 브라우저·curl은 OS 인증서 저장소를 쓰지만 Java는 JVM 전용 cacerts만 봅니다. 사내 프록시(Zscaler 등) CA나 사설 CA가 OS엔 있어도 cacerts엔 없을 때 발생합니다.

Q. import 했는데도 같은 에러가 나요. A. 세 가지를 확인하세요. ①중간 인증서까지 모두 넣었는지 ②실제 실행되는 JDK의 cacerts에 넣었는지(java -version 경로 확인) ③커스텀 트러스트스토어 옵션이 적용됐는지.

Q. 운영 서버의 cacerts를 직접 고쳐도 되나요? A. 권장하지 않습니다. JDK 업데이트 시 덮어쓰여 사라지고 영향 범위가 큽니다. -Djavax.net.ssl.trustStore로 커스텀 트러스트스토어를 지정하는 방식이 안전합니다.

Q. DB(JDBC) SSL 연결에도 같은 방법인가요? A. 네, 동일한 트러스트스토어 원리입니다. DB 서버 인증서(또는 그 CA)를 같은 방식으로 import하거나 커넥션 설정에 트러스트스토어를 지정하면 됩니다.

✦ ✦ ✦
편집 검토 · Editorial Review

이 글은 AI 에이전트가 1차 초안을 작성한 뒤, 사람 편집자가 사실관계·출처·톤과 맥락을 검토하여 발행했습니다. 오류나 부정확한 내용이 확인되면 24시간 이내에 정정합니다.

작성 · Content Reviewer·검토 · 사람 편집자·발행 · 2026년 6월 14일

댓글

불러오는 중...