- Published on
Python SSL CERTIFICATE_VERIFY_FAILED 10분 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서드파티 API 호출, 패키지 설치(pip), 내부 HTTPS 엔드포인트 접근 등에서 갑자기 아래 오류를 만나면 대부분 “내 코드” 문제가 아니라 신뢰할 수 있는 CA(인증기관) 체인을 Python이 찾지 못하는 환경 문제입니다.
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed
이 글은 “일단 되게 만들기”가 아니라 10분 안에 원인을 분류하고, 가장 안전한 방식으로 고치는 루트를 제공합니다. 마지막에 정말 불가피할 때만 쓰는 임시 우회도 함께 정리합니다.
> 참고로 네트워크/프록시가 개입된 장애는 타임아웃/재시도 설계와 함께 봐야 재발이 줄어듭니다. 연결 오류 대응은 Python httpx ReadTimeout·ConnectError 재시도 설계도 같이 확인해두면 좋습니다.
1) 10분 진단 체크리스트(가장 빠른 분기)
아래 4가지만 확인해도 80%는 바로 갈립니다.
어디서 터지나?
pip install중인가?requests/httpx/aiohttp로 API 호출 중인가?- 특정 도메인에서만 터지나?
환경이 뭔가?
- macOS 로컬 Python?
- Ubuntu/Alpine Docker 컨테이너?
- 회사 프록시(SSL 검사) / 사내망?
- AWS Lambda/EKS 같은 런타임?
서버 인증서 체인 문제인가? (서버가 중간 인증서 미제공)
클라이언트 CA 번들 문제인가? (컨테이너에 CA 번들이 없음/오래됨)
진단을 위해 다음 스니펫을 먼저 실행해 “Python이 어떤 CA 번들을 쓰는지”부터 확인합니다.
import ssl
import certifi
print("ssl default verify paths:", ssl.get_default_verify_paths())
print("certifi.where():", certifi.where())
ssl.get_default_verify_paths()가 가리키는 경로에 파일이 없거나,- 컨테이너가 minimal 이미지라 CA 패키지가 없거나,
- 회사 프록시가 자체 CA로 TLS를 재서명하는데 그 CA가 신뢰 저장소에 없으면
대부분 CERTIFICATE_VERIFY_FAILED로 귀결됩니다.
2) 가장 흔한 원인 1: Docker/Alpine에 CA 인증서가 없다
증상
- 로컬에서는 되는데 컨테이너에서만 실패
- 특히
python:3.x-alpine,distroless, 최소 이미지에서 빈번
해결(권장): CA 번들 설치
Alpine
FROM python:3.12-alpine
RUN apk add --no-cache ca-certificates \
&& update-ca-certificates
# (선택) pip가 HTTPS 접근 시에도 안전
RUN python -m pip install --upgrade pip
Debian/Ubuntu
FROM python:3.12-slim
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates \
&& update-ca-certificates \
&& rm -rf /var/lib/apt/lists/*
컨테이너에서 CA 번들이 설치되면 requests/httpx는 OS trust store를 통해 정상 검증합니다.
추가 확인: 인증서 파일이 실제로 존재하는지
python - <<'PY'
import ssl
p = ssl.get_default_verify_paths()
print(p)
PY
ls -al /etc/ssl/certs || true
ls -al /etc/pki/tls/certs || true
3) 가장 흔한 원인 2: 회사 프록시(SSL Inspection)로 인증서가 재서명된다
증상
- 사내 네트워크에서만 실패, 집/모바일 핫스팟에서는 정상
- 브라우저는 잘 되는데 Python만 실패(브라우저에는 회사 Root CA가 이미 배포됨)
해결(권장): 회사 Root CA를 OS/Python 신뢰 저장소에 추가
회사 보안 장비가 TLS를 중간에서 복호화/재암호화하면, 서버 인증서는 “공인 CA”가 아니라 “회사 CA”로 서명됩니다. 따라서 Python이 그 CA를 신뢰하도록 해야 합니다.
(A) OS trust store에 추가 (가장 바람직)
- Debian/Ubuntu:
/usr/local/share/ca-certificates/*.crt에 넣고update-ca-certificates
sudo cp corp-root-ca.crt /usr/local/share/ca-certificates/corp-root-ca.crt
sudo update-ca-certificates
- RHEL/CentOS 계열:
/etc/pki/ca-trust/source/anchors/에 넣고update-ca-trust
sudo cp corp-root-ca.crt /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust
(B) 앱 레벨에서 CA 번들 지정(운영에서 자주 쓰는 방식)
requests/httpx는 CA 번들을 파일로 지정할 수 있습니다.
import httpx
r = httpx.get(
"https://api.example.com",
verify="/etc/ssl/certs/corp-bundle.pem",
timeout=10,
)
print(r.status_code)
환경변수로도 지정 가능합니다.
export SSL_CERT_FILE=/etc/ssl/certs/corp-bundle.pem
# 또는
export REQUESTS_CA_BUNDLE=/etc/ssl/certs/corp-bundle.pem
> 프록시가 있는 환경에서는 “연결은 되는데 응답이 끊김/오류 코드가 튀는” 문제도 함께 생깁니다. 스트리밍/장시간 연결이라면 프록시 튜닝 관점에서 LLM SSE 스트리밍 499 502 급증과 응답 끊김을 잡는 프록시 튜닝 체크리스트도 같이 보면 원인 분리가 빨라집니다.
4) 가장 흔한 원인 3: 서버가 중간 인증서(Intermediate)를 누락했다
증상
- 특정 도메인에서만 실패
- 브라우저는 “알아서” 보완해 보이기도 하지만, Python은 엄격하게 실패
확인: openssl로 체인 검증
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null
출력에서 Verify return code: 0 (ok)가 아니면 서버 측 체인 문제일 가능성이 큽니다.
해결
- 서버(예: Nginx/ALB/Ingress)에 fullchain(서버 인증서 + intermediate)을 설정
- Let’s Encrypt라면 보통
fullchain.pem을 사용
예: Nginx
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
}
이 경우 클라이언트(Python)를 건드리는 게 아니라 서버 설정을 고치는 게 정답입니다.
5) macOS에서만 나는 경우: Python 인증서 번들 설치(특히 python.org 설치본)
macOS에서 python.org에서 받은 Python을 쓰면, 시스템 키체인과 Python 번들이 분리되어 인증서가 비어 있는 케이스가 있습니다.
해결
/Applications/Python 3.x/아래에 있는Install Certificates.command실행- 또는
certifi를 사용해 verify 경로를 명시
import requests
import certifi
resp = requests.get("https://pypi.org/simple", verify=certifi.where(), timeout=10)
print(resp.status_code)
6) pip install에서 터질 때(패키지 설치 자체가 막힘)
6-1) CA 업데이트
가장 먼저 ca-certificates 설치/업데이트를 확인하세요(컨테이너면 2번 참고).
6-2) 사내 미러/프록시 사용
사내에서 PyPI를 직접 못 나가면 미러를 쓰거나 프록시 설정이 필요합니다.
pip config set global.index-url https://pypi.company.local/simple
pip config set global.trusted-host pypi.company.local
> trusted-host는 TLS 검증 자체를 끄는 게 아니라 “호스트 검증 예외”에 가까운 설정이라 보안적으로도 주의가 필요합니다. 가능하면 사내 CA를 신뢰하도록 구성하는 편이 낫습니다.
7) (정말 마지막 수단) 검증 비활성화는 이렇게, 그리고 왜 위험한가
긴급 장애 대응 중 임시로만 쓰고, 티켓/리마인더를 남겨 반드시 원인 해결로 되돌리세요.
requests
import requests
resp = requests.get("https://api.example.com", verify=False, timeout=10)
print(resp.status_code)
httpx
import httpx
with httpx.Client(verify=False, timeout=10) as client:
r = client.get("https://api.example.com")
print(r.status_code)
위험성
- 중간자 공격(MITM)에 취약
- 사내 프록시 환경에서는 “누가 재서명했는지” 구분 불가
- 토큰/쿠키/자격증명 유출 가능
운영 환경에서는 “회사 CA 추가” 또는 “서버 fullchain 구성”이 정석입니다.
8) 10분 해결 루트 요약(가장 성공률 높은 순서)
- 컨테이너/서버라면
ca-certificates설치 및 업데이트 - 사내망이라면 회사 Root CA를 OS trust store에 추가하거나
SSL_CERT_FILE로 지정 - 특정 도메인만 문제면
openssl s_client로 서버 체인(fullchain) 누락 여부 확인 - macOS라면
Install Certificates.command또는certifi.where()로 검증 경로 고정 - 임시로만
verify=False(운영 금지에 가깝게 취급)
9) 재발 방지 체크(운영 관점)
- Docker 베이스 이미지 변경 시(CI에서 slim/alpine로 바꿀 때)
ca-certificates설치가 빠지지 않게 Dockerfile 템플릿화 - 사내 프록시 사용 조직이라면 회사 CA 배포/로테이션 절차를 문서화하고, 컨테이너 이미지 빌드 단계에서 CA 주입
- 외부 API 의존 서비스는 TLS 오류가 곧 장애로 이어지므로, 재시도/타임아웃/서킷브레이커를 함께 설계(위에서 언급한 httpx 재시도 글 참고)
이 순서대로 따라가면 대부분의 SSL CERTIFICATE_VERIFY_FAILED는 “원인에 맞는 방식”으로 10분 내 정리됩니다.