Mensagem de erro
[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate
Também aparece como
- ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate
O Python acrescenta a localização no código-fonte em C, por exemplo, “(_ssl.c:1082)”; o número muda com a versão do Python e não significa nada para o diagnóstico. O urllib encapsula o erro como “<urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] …>”, e o requests e o urllib3, como “Max retries exceeded … (Caused by SSLError(…))”.
Python ssl module (OpenSSL) — ssl.SSLCertVerificationError, X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY (20)
O que significa
O módulo ssl do Python pediu ao OpenSSL que verificasse o certificado do servidor, e o OpenSSL não encontrou o emissor do último certificado da cadeia entre os certificados de CA que este SSLContext carregou. O servidor não está necessariamente mal configurado: muitas vezes este Python simplesmente consulta um bundle de CAs diferente (ou vazio) daquele que seu navegador ou seu sistema operacional usa.
Por que acontece
O Python do python.org no macOS sem os próprios certificados
comumO instalador do python.org para macOS traz o próprio OpenSSL, que não lê o chaveiro do macOS (Keychain). Até que “Install Certificates.command” seja executado para essa versão do Python, ela não tem nenhum certificado de CA, então toda requisição HTTPS falha desse jeito.
Uma CA privada ou um proxy com inspeção de TLS
comumServiços da empresa e proxies corporativos usam certificados cuja raiz é distribuída pelo repositório de certificados do sistema operacional às máquinas gerenciadas. O OpenSSL do Python não consulta o repositório do Windows nem o do macOS, e contêineres Linux não têm a raiz, a menos que ela seja adicionada.
O servidor não envia o certificado intermediário
comumO OpenSSL não busca intermediários ausentes. Se o servidor envia apenas o certificado folha, o Python falha, enquanto os navegadores, que armazenam intermediários em cache e os baixam, parecem funcionar.
A CA foi adicionada ao repositório do sistema, mas a biblioteca usa o certifi
ocasionalO requests, o httpx, o aiohttp, o botocore e o pip (no modo de certificados legados) verificam com o bundle do certifi, distribuído como pacote Python. Uma raiz instalada com update-ca-certificates ou no chaveiro do sistema operacional fica invisível para eles.
SSL_CERT_FILE ou SSL_CERT_DIR aponta para o lugar errado
raraEssas variáveis substituem os locais padrão do OpenSSL. Um caminho que sobrou de outra ferramenta, ou um bundle que contém só uma raiz privada, faz os sites públicos falharem (ou o contrário).
Como corrigir
- 1.
Builds do python.org para macOS: executar Install Certificates.command
O script instala o certifi e o vincula como bundle do OpenSSL desse Python. Ele precisa ser executado de novo depois da instalação de outra versão do Python.
bash # macOS, python.org installer: run once per installed Python version "/Applications/Python 3.14/Install Certificates.command" - 2.
Usar o trust store do sistema operacional com o truststore
O truststore faz o ssl.SSLContext verificar pelo sistema operacional (o chaveiro no macOS, o repositório de certificados do Windows, o bundle do sistema no Linux), de modo que as raízes implantadas pelo seu departamento de TI também funcionem no Python. Ele também cobre o urllib3, o requests e o pip, que o usa por padrão desde o pip 24.2.
python # pip install truststore==0.10.5 — verify with the OS trust store (Keychain, Windows store, Linux bundle) import truststore truststore.inject_into_ssl() # call once at start-up, before creating connections import urllib.request urllib.request.urlopen("https://api.example.com/") - 3.
Carregar a CA explicitamente para um único cliente
Passe cafile (ou cadata) para ssl.create_default_context() no código que se comunica com um serviço privado. O contexto continua verificando nomes de host e datas.
python import ssl, urllib.request ctx = ssl.create_default_context(cafile="/etc/ssl/private-ca/corp-root-ca.pem") urllib.request.urlopen("https://api.example.com/", context=ctx) - 4.
Conferir e definir o caminho do bundle padrão
ssl.get_default_verify_paths() mostra qual arquivo e qual diretório o OpenSSL usa. SSL_CERT_FILE e SSL_CERT_DIR substituem esses valores para o processo; aponte essas variáveis para um bundle que contenha tanto as raízes públicas quanto a sua CA.
bash # Where does this Python look by default? python3 -c "import ssl; print(ssl.get_default_verify_paths())" # Point OpenSSL-based clients at a bundle that includes your CA export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt - 5.
Donos do servidor: enviar a cadeia completa
Se o handshake contém só um certificado e o emissor dele é um intermediário público, configure o servidor com o arquivo da cadeia completa.
bash # Server side: 1 certificate in the handshake = intermediate missing openssl s_client -connect api.example.com:443 -servername api.example.com -showcerts </dev/null 2>/dev/null \ | grep -c 'BEGIN CERTIFICATE'
Erros relacionados
<urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unab…Python urllib.requestMax retries exceeded with url: / (Caused by SSLError(SSLCertVerificationError(1…requests / urllib3 (Python)There was a problem confirming the ssl certificate: [SSL: CERTIFICATE_VERIFY_FA…pip[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certifi…Python ssl module (OpenSSL)curl: (60) SSL certificate problem: unable to get local issuer certificatecurl (OpenSSL backend)