🔍 검색 키워드: CERTIFICATE_VERIFY_FAILED · Python SSL 에러 · ssl.SSLCertVerificationError · Python requests SSL 오류 · macOS Python SSL · Python urllib SSL 인증서 오류
Python으로 외부 API 호출하거나 크롤링하다 보면 이 에러 한 번쯤은 만난다.
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED]
certificate verify failed: unable to get local issuer certificate (_ssl.c:1129)
이거 처음 보면 당황스럽다. 에러 메시지만 봐서는 뭐가 문제인지 감도 안 온다. 원인부터 단계별 해결까지 정리한다.
왜 이 에러가 나는가
Python이 HTTPS 요청을 보낼 때 서버의 SSL 인증서를 검증한다. 이 과정에서 신뢰할 수 있는 루트 인증서(CA) 목록을 참조하는데, 여기에 서버 인증서 체인이 없으면 에러가 난다.
주요 원인은 크게 세 가지다.
- Python 자체 CA 번들이 오래됨 — macOS에서 Python 3.6+ 공식 배포판은 시스템 인증서를 쓰지 않고 번들로 따로 관리하는데, 이게 업데이트 안 되면 발생한다.
- 회사 네트워크의 프록시/방화벽 — 기업 환경에서 HTTPS 트래픽을 자체 인증서로 중간에서 가로채는(MITM) 경우, Python이 그 인증서를 모른다.
- 자체 서명(self-signed) 인증서를 가진 서버 — 내부 개발 서버나 테스트 환경에서 자주 발생.
상황별 체크리스트
| 상황 | 원인 | 권장 해결책 |
|---|---|---|
| macOS + Python 공식 설치 | 번들 CA 미업데이트 | Install Certificates.command 실행 |
| 회사 내부 네트워크 | 기업 프록시 인증서 | 기업 CA 인증서 추가 |
| 내부 개발 서버 | self-signed 인증서 | verify=인증서경로 지정 |
| Docker/CI 환경 | CA 번들 미포함 | certifi 패키지 + 환경변수 설정 |
| 갑자기 발생 (예전엔 됐는데) | Python 또는 OS 업그레이드 | certifi 재설치 |
레벨별 해결 방법
초보자 — 일단 돌아가게 만들기 (비추천)
개발 중에 빠르게 확인만 할 때 쓰는 방법이다. 절대 프로덕션에 쓰면 안 된다.
import requests
# SSL 검증 비활성화 — 개발용으로만!
response = requests.get("https://example.com", verify=False)
중급자 — certifi로 CA 번들 업데이트
가장 권장하는 방법이다. certifi는 Mozilla가 관리하는 신뢰할 수 있는 CA 목록을 Python에서 쓸 수 있게 패키징한 라이브러리다.
pip install --upgrade certifi
import requests
import certifi
response = requests.get("https://example.com", verify=certifi.where())
print(response.status_code)
환경변수로 전역 적용 (추천)
# Linux/macOS
export SSL_CERT_FILE=$(python -c "import certifi; print(certifi.where())")
export REQUESTS_CA_BUNDLE=$(python -c "import certifi; print(certifi.where())")
macOS 전용 — Install Certificates.command 실행
# 해당 버전의 Install Certificates 스크립트 실행 (버전에 맞게 경로 수정)
open /Applications/Python\ 3.12/Install\ Certificates.command
고급자 — 기업 CA 인증서 추가
import requests
# 단일 파일
response = requests.get("https://internal.company.com", verify="/path/to/company-ca.crt")
import certifi
with open("/path/to/company-ca.crt", "r") as f:
company_cert = f.read()
with open(certifi.where(), "a") as bundle:
bundle.write("\n" + company_cert)
Docker/CI 환경
FROM python:3.12-slim
RUN pip install certifi
ENV SSL_CERT_FILE=/usr/local/lib/python3.12/site-packages/certifi/cacert.pem
ENV REQUESTS_CA_BUNDLE=/usr/local/lib/python3.12/site-packages/certifi/cacert.pem
# GitHub Actions
- name: Fix SSL certificates
run: |
pip install --upgrade certifi
echo "SSL_CERT_FILE=$(python -c 'import certifi; print(certifi.where())')" >> $GITHUB_ENV
echo "REQUESTS_CA_BUNDLE=$(python -c 'import certifi; print(certifi.where())')" >> $GITHUB_ENV
urllib / aiohttp 사용자 참고
import ssl, urllib.request, certifi
ctx = ssl.create_default_context(cafile=certifi.where())
req = urllib.request.Request("https://example.com")
with urllib.request.urlopen(req, context=ctx) as response:
data = response.read()
import aiohttp, ssl, certifi
async def fetch(url):
ssl_ctx = ssl.create_default_context(cafile=certifi.where())
connector = aiohttp.TCPConnector(ssl=ssl_ctx)
async with aiohttp.ClientSession(connector=connector) as session:
async with session.get(url) as response:
return await response.text()
절대 하면 안 되는 것
⚠️ 프로덕션에서 이런 코드 보이면 반드시 수정해야 한다.
# ❌ 절대 하지 말 것
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
# ❌ 이것도 마찬가지
requests.get(url, verify=False)
# ❌ 환경변수로 전체 비활성화
# PYTHONHTTPSVERIFY=0
SSL 검증을 끄는 순간 중간자 공격(MITM)에 완전히 노출된다. 개발 편의를 위해 껐다가 프로덕션에 그대로 배포되는 사고가 실제로 발생한다.
정리
| 우선순위 | 해결책 | 상황 |
|---|---|---|
| 1순위 | Install Certificates.command | macOS + 공식 Python 설치 |
| 2순위 | pip install --upgrade certifi + 환경변수 | 대부분의 환경 |
| 3순위 | 기업 CA 인증서 추가 | 회사 내부 네트워크 |
| 4순위 | verify=False | 로컬 개발 일시적 확인 (프로덕션 절대 불가) |
💡 실무에서 이 에러를 자주 만나는 패턴은 Python 버전 업그레이드 직후, 또는 새 팀원이 회사 환경 세팅할 때다. 팀 위키에 이 내용 정리해두면 반복 질문 많이 줄어든다.