ssl.SSLCertVerificationError: certificate verify failed 해결
ssl.SSLCertVerificationError: certificate verify failed는 Python이 인증서 체인을 신뢰하지 못한 것입니다. certifi·macOS 인증서 명령·서버 중간 인증서를 순서대로 점검합니다. 무료 즉시 진단으로 바로 확인.
내 도메인에 이 문제가 있는지 지금 확인
무료, 가입 불필요. 이 가이드가 다루는 항목을 바로 검사하고 조치 방법을 알려드립니다.
문제
Python이 ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1006)로 멈추고 TLS 연결을 마치지 못합니다. 이건 OpenSSL의 검증 결과 20(X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY)이 Python ssl 모듈을 통해 드러난 것입니다. Python은 인증서를 받았지만, 그것으로부터 자신이 신뢰하는 인증기관까지 이어지는 체인을 세우지 못했습니다. 인증서 자체는 대개 멀쩡합니다. 빠진 건 연결 고리 하나이고, 그 고리가 빠질 수 있는 곳은 둘뿐입니다 — 서버(리프를 공개 루트로 잇는 중간 인증서를 안 보냄) 또는 내 컴퓨터(Python 신뢰 저장소에 루트가 없거나, 무언가가 Python이 모르는 CA로 트래픽을 재서명함). 이 둘 중 어느 쪽이 고리를 빠뜨렸는지 가려내는 게 전부입니다.
증상
- 전체 메시지는 이유로 끝납니다.
unable to get local issuer certificate(검증 오류 20)가 흔합니다 — 신뢰 루트에 닿지 못하는 체인.certificate has expired(오류 10),self-signed certificate in certificate chain(오류 19, 경로에 사설·가로채기 CA),self-signed certificate(오류 18, 엔드포인트가 자기 루트를 제시)는 서로 다른 문제를 가리키니, 뭔가 하기 전에 메시지 끝부분을 읽으세요. ssl위에 얹힌 라이브러리를 통해 드러납니다.requests는SSLError(... CERTIFICATE_VERIFY_FAILED ...)로,urllib·pip·httpx는 같은 하위 오류를 감싸 보여줍니다.- 한 호스트만 실패하고 나머지는 정상이면: 문제는 그 호스트의 체인 — 거의 항상 서버의 중간 인증서 누락.
- 모든 HTTPS 호스트에서 실패하거나, macOS에 Python을 막 설치한 직후라면: 문제는 내 컴퓨터의 Python 신뢰 저장소이지 어느 서버도 아닙니다.
주요 원인 3가지
- Python이 유효한 CA 번들을 읽지 못함 (macOS/Windows 고전) - python.org 빌드는 자체 OpenSSL을 실어 배포하고 루트를 OS 저장소가 아니라
certifi번들에서 신뢰합니다. macOS 새 설치에선 설치 프로그램의Install Certificates.command를 돌리기 전까지 번들이 연결되지 않아 모든 HTTPS 요청이 실패합니다.ca-certificates패키지가 없는 최소 Docker 이미지·갓 프로비저닝한 Linux, 루트 교체 이전 버전인 오래된certifi에서도 똑같이 물립니다. - 서버가 중간 인증서를 안 보냄 - 한 호스트만 실패하고 나머지는 정상일 때 가장 흔한 원인. 서버가 리프만 제시하고 신뢰 루트로 잇는 중간 인증서를 뺍니다. 브라우저는 중간 인증서를 스스로 받아와(AIA fetching) 이를 가려주지만 Python은 안 합니다. 그래서 체인을 완성하지 못하고 오류 20으로 멈춥니다. 수정은 서버에서 — 리프만이 아니라 풀체인을 배포하세요.
- 프록시나 백신이 TLS를 가로챔 - 사내 미들박스와 “HTTPS 검사” 백신은 트래픽을 복호화한 뒤 자기 사설 CA로 재서명합니다. 그 CA는 관리 대상 기기의 OS 저장소엔 있지만, Python은 OS 저장소가 아니라 certifi를 읽으므로 찾을 수 없는 발급자로 보입니다 — 보통
self-signed certificate in certificate chain(오류 19). 관리 대상이 아닌 기기라면 같은 서명이 곧 실제 공격자가 만들어낼 서명이므로 Python이 거부하는 게 맞습니다.
DechoNet으로 진단
- SSL 검사는 서버가 실제로 회선에 실어 보내는 체인을 그대로 보여줍니다 — 중간 인증서가 있는지, 서버가 리프만 보내는지. 여기서 체인이 불완전하면 원인 #2가 확정이고 수정은 서버에서 합니다. 공개 인터넷에서는 체인이 완전한데 내 컴퓨터의 Python만 실패한다면 문제는 로컬입니다 —
certifi번들 또는 트래픽을 재서명하는 프록시(원인 #1 또는 #3). - HTTP 검사는 인증서 문제를 제쳐두고 엔드포인트가 실제로 응답하는지 확인해줘서, 함께 죽어 있는 호스트를 두고 신뢰 오류를 쫓지 않게 해줍니다.
해결 체크리스트
- 범위부터 읽으세요. 한 호스트만 실패하고 나머지는 정상 → 서버 체인 문제. 모든 호스트 실패 → Python 신뢰 저장소. 이 한 질문이 수정 전체의 방향을 정합니다.
- macOS의 새 python.org 설치라면 동봉된 명령을 한 번 실행:
/Applications/Python\ 3.x/Install\ Certificates.command(버전은 본인 것으로).certifi를 pip 설치하고 Python 기본 검증을 거기에 연결합니다. “Python 설치 직후 모든 호스트가 실패” 케이스는 이걸로 끝납니다. - 그 외 환경에선 번들 갱신:
pip install --upgrade certifi. 그다음 Python이 실제로 뭘 읽는지 확인 —python -c "import ssl; print(ssl.get_default_verify_paths())",python -c "import certifi; print(certifi.where())". - 특정 라이브러리를 검증된 번들로 지정하려면 명시적으로 넘기세요:
requests.get(url, verify=certifi.where()), 또는 환경변수를 읽는 도구엔SSL_CERT_FILE·REQUESTS_CA_BUNDLE를 번들 경로로 설정.verify=False와 달리 검증을 켜둔 채로 갑니다. - 한 호스트가 실패하면 서버가 보내는 체인을 보세요 —
openssl s_client -connect YOUR_DOMAIN:443 -servername YOUR_DOMAIN -showcerts.CERTIFICATE블록이 하나만 보이면 중간 인증서가 빠진 것 — 서버에서 인증서를 풀체인 번들로 다시 설치하고 SSL 검사로 중간 인증서가 이제 나타나는지 확인하세요. - 신뢰해야 하는 사내 프록시라면 그 루트를 Python이 읽는 번들에 추가하세요(프록시 CA의 PEM을 덧붙이거나,
REQUESTS_CA_BUNDLE를 그걸 포함한 파일로 설정). 가로채는 CA가 낯설다면 멈추고, 무턱대고 신뢰하지 말고 의심하세요.
언제 에스컬레이션할까
- SSL 검사가 공개 인터넷에서 완전하고 올바른 체인을 보여주는데도 특정 한 기기에서만 Python이 실패한다면, 재서명하는 CA는 그 기기의 네트워크에 있습니다 — 프록시나 백신. 어느 루트를 신뢰하고 어떻게 배포할지는 엔드포인트/IT 문제이지 스크립트별 플래그가 아닙니다.
- 엔드포인트가 정말로 사설·자체 서명 CA를 쓴다면(사내 서비스, 어플라이언스, 개발 환경)
verify=/path/to/ca.pem이나 certifi에 추가해 그 특정 CA만 신뢰하세요 — 검증을 전역으로 끄지 마세요.verify=False나NODE_TLS_REJECT_UNAUTHORIZED류의 탈출구에 손을 뻗는 습관은, 내 진짜 서버와 사칭범을 가르는 유일한 신호를 무시하도록 당신을 길들입니다.
관련 도구
관련 가이드
가이드 공유