---
title: "Unable to Get Local Issuer Certificate: como resolver"
description: "Unable to get local issuer certificate: sua ferramenta não encontra a CA que assinou a cadeia do servidor. Causas e soluções no Python, npm, Git e curl."
url: https://proxynet.io/pt-br/blog/unable-to-get-local-issuer-certificate
date: 2026-10-06
author: "Acar Diveroli"
category: "Tutoriais, Web scraping"
lang: pt-BR
---

# Unable to Get Local Issuer Certificate: como resolver

Você roda `npm install` em um notebook do trabalho e o comando para com `npm error code UNABLE_TO_GET_ISSUER_CERT_LOCALLY`. O registry abre normalmente no Chrome na mesma máquina, e um colega que trabalha de casa instala os mesmos pacotes sem problema. Um script Python falha com `CERTIFICATE_VERIFY_FAILED`, e o `git clone` informa um problema de certificado SSL. Não há nada de errado com o registry: suas ferramentas e seu navegador confiam em listas diferentes de autoridades certificadoras.

Este guia explica o que o cliente verifica, separa as três causas com erros que reproduzimos em servidores de teste locais e mostra a correção para Python, npm, Git e curl, além da correção no lado do servidor.

> **Nota: Resposta rápida**
>
> Seu cliente não conseguiu ligar a cadeia de certificados do servidor a uma raiz da própria lista de raízes confiáveis, o repositório de confiança local (trust store). Três causas: o servidor deixa de enviar o certificado intermediário; um proxy ou antivírus com inspeção TLS assina o tráfego de novo com uma raiz que a sua ferramenta não tem; ou a sua ferramenta lê o próprio bundle de CA em vez do repositório do sistema. Adicione a raiz certa onde a ferramenta procura (`truststore` ou `REQUESTS_CA_BUNDLE`, `NODE_EXTRA_CA_CERTS`, Schannel ou `http.sslCAInfo`, `--cacert`) ou corrija a cadeia do servidor. `verify=False` e `-k` só escondem o erro.

## O que significa "unable to get local issuer certificate"?

Um certificado TLS informa o titular (o subject) e a autoridade certificadora (CA) que o assinou (o issuer, ou emissor). Um servidor envia o próprio certificado, chamado de certificado folha (leaf), mais um ou mais certificados intermediários que o ligam a um certificado raiz. As raízes não são enviadas durante a conexão: cada cliente mantém o próprio conjunto de raízes confiáveis, o repositório de confiança, e esse conjunto é o "local" da mensagem.

O texto é o erro de verificação 20 do OpenSSL, `X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY`. O cliente chegou a um certificado cujo emissor não encontrou nem entre os certificados que o servidor enviou nem no próprio repositório. O handshake TLS para antes de a sua requisição ser enviada.

## Como funciona a verificação da cadeia de certificados?

1. **O servidor envia a cadeia.** O certificado folha vem primeiro, cada certificado seguinte assina o anterior, e a raiz pode ficar de fora porque os clientes já a têm ([RFC 8446, seção 4.4.2](https://www.rfc-editor.org/rfc/rfc8446.html#section-4.4.2)).
2. **O cliente verifica o certificado folha:** o nome precisa corresponder ao host da URL, e as datas precisam ser válidas.
3. **O cliente encontra cada emissor** entre os certificados enviados, confere a assinatura e repete um nível acima.
4. **O topo precisa terminar no repositório de confiança,** assinado por uma raiz em que o cliente já confia.
5. **Um elo faltando interrompe o handshake,** e o texto depende de onde a cadeia parou.

O repositório de confiança depende da ferramenta, não da máquina. O Requests usa o certifi, um pacote Python com a lista de raízes da Mozilla; o Node.js embute em cada versão uma cópia do repositório da Mozilla; o Git for Windows lê o próprio `ca-bundle.crt` ou o repositório do Windows. Por isso um programa pode falhar onde outro funciona no mesmo notebook.

## Três mensagens de erro, uma mesma família

Criamos com o OpenSSL uma raiz, um intermediário e um certificado folha, rodamos três servidores HTTPS locais que enviam partes diferentes da cadeia e os chamamos no Windows 11 a partir do Python 3.13.9 (Requests 2.34.2), Node.js 24.11.1 (npm 11.6.2), curl 8.21.0 e Git 2.55 com OpenSSL. Nenhuma ferramenta conhecia a nossa raiz.

| O que o servidor enviou | Python (`ssl`, Requests) | Node.js e npm | curl e Git (OpenSSL) |
|---|---|---|---|
| Folha + intermediário | `unable to get local issuer certificate` | `UNABLE_TO_GET_ISSUER_CERT_LOCALLY` | `unable to get local issuer certificate (20)` |
| Só a folha | `unable to get local issuer certificate` | `UNABLE_TO_VERIFY_LEAF_SIGNATURE` | `unable to get local issuer certificate (20)` |
| Folha + intermediário + raiz | `self-signed certificate in certificate chain` | `SELF_SIGNED_CERT_IN_CHAIN` | `self-signed certificate in certificate chain (19)` |

Python e curl usam as mesmas palavras para um intermediário ausente e para uma raiz desconhecida, então só a cadeia mostra qual lado corrigir. A terceira linha é o mesmo problema com a raiz incluída: o servidor, ou um proxy no meio do caminho, enviou uma raiz em que a sua ferramenta não confia.

## As três causas do erro

### 1. O servidor deixa de enviar o certificado intermediário

O administrador instalou só o certificado folha, então todo cliente sem o intermediário falha, em qualquer rede. Os navegadores costumam esconder isso: a Mozilla explica que o Firefox já traz os intermediários conhecidos, enquanto outros navegadores baixam o que falta em segundo plano. Python, Node.js, curl e Git não fazem nem uma coisa nem outra, então "funciona no Chrome" não prova nada.

### 2. Um proxy ou antivírus com inspeção TLS assina o tráfego de novo

Gateways web de empresas, antivírus com verificação de HTTPS e ferramentas de depuração como mitmproxy, Charles e Fiddler abrem a conexão criptografada e apresentam um certificado novo para o site, assinado pela própria raiz. A TI instala essa raiz no repositório do sistema dos notebooks gerenciados, então os navegadores a aceitam; as ferramentas com lista própria, não. Pistas: o erro aparece na rede do escritório ou na VPN, não em casa, e atinge quase todos os sites. Se você mesmo usa uma ferramenta dessas, o passo do certificado raiz está em [O que é um proxy MITM? Guia de Charles, Fiddler e mitmproxy](/pt-br/blog/mitm-proxy).

### 3. Sua ferramenta confia em uma lista diferente da do sistema

Os bundles do Requests, do Node.js e do Git nunca veem uma raiz que a sua empresa instalou no Windows ou no macOS, nem uma CA privada que assina serviços internos. O Python do instalador do python.org para macOS precisa de uma etapa própria de certificados pelo mesmo motivo, e sistemas e bundles desatualizados podem não ter raízes públicas mais novas.

## Como descobrir qual é a sua causa?

Veja a cadeia que o servidor realmente envia. O Git for Windows traz o `openssl`, então isto também roda no Git Bash:

```bash
openssl s_client -connect localhost:47444 -servername localhost -showcerts </dev/null
```

Nosso servidor na porta 47444 envia só o certificado folha. As linhas relevantes:

```text
Certificate chain
 0 s:CN=localhost
   i:O=Example Corp, CN=Example Corp Issuing CA
Verify return code: 21 (unable to verify the first certificate)
```

`s:` é o titular (subject) e `i:` é o emissor (issuer); uma cadeia completa acrescenta uma entrada `1 s:`. Para o caso da inspeção, rodamos um proxy local que assina o tráfego de novo como um gateway de empresa, com uma CA chamada Example Corp, e pedimos `example.com` por ele (`-proxy 127.0.0.1:47461`):

```text
Certificate chain
 0 s:CN=example.com
   i:O=Example Corp, CN=Example Corp Issuing CA
 1 s:O=Example Corp, CN=Example Corp Issuing CA
   i:O=Example Corp, CN=Example Corp Root CA
Verify return code: 20 (unable to get local issuer certificate)
```

Por um túnel comum, o mesmo comando mostrou a cadeia real do site, emitida por `Cloudflare TLS Issuing ECC CA 3`, e `Verify return code: 0 (ok)`. Leia a sua saída assim:

- **Só a entrada 0, emissor público:** o servidor deixa de enviar o intermediário (causa 1).
- **O emissor é a sua empresa, um produto de segurança ou uma ferramenta de depuração:** algo assina o tráfego de novo (causa 2).
- **Cadeia pública completa e `0 (ok)`, mas a sua ferramenta falha:** a ferramenta lê outro repositório de confiança (causa 3).

## Como resolver no Python?

O Requests embrulha o erro em uma linha mais longa; [Max Retries Exceeded With URL: o que é e como resolver](/pt-br/blog/max-retries-exceeded-with-url) explica como ler a parte `Caused by`. Nosso teste gerou:

```text
requests.exceptions.SSLError: HTTPSConnectionPool(host='localhost', port=47443): Max retries exceeded with url: / (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1032)')))
```

Se a TI instalou a raiz da empresa no sistema operacional, deixe o Python usar esse repositório. O pacote `truststore` (Python 3.10 ou posterior) faz o Requests e o módulo `ssl` verificarem pelo repositório do sistema; a documentação dele restringe `inject_into_ssl()` a aplicações e scripts, não a bibliotecas:

```python
import truststore
truststore.inject_into_ssl()  # chame uma vez, no início do seu script

import requests

print(requests.get("https://example.com/", timeout=10).status_code)
```

Caso contrário, monte um bundle com as raízes do certifi mais a raiz da empresa e aponte `REQUESTS_CA_BUNDLE` para ele:

```bash
cat "$(python -m certifi)" corp-root.pem > ca-bundle-plus-corp.pem
export REQUESTS_CA_BUNDLE="$PWD/ca-bundle-plus-corp.pem"
```

Depois disso, tanto `example.com` quanto o nosso servidor de teste retornaram `200`. Nunca use a raiz da empresa sozinha: a variável substitui o certifi, e no nosso teste `example.com` passou a falhar com o mesmo erro. Gere o arquivo de novo depois de cada atualização do certifi.

Diferente do Requests, o pip também usa os certificados do sistema desde a versão 24.2 ([documentação do pip](https://pip.pypa.io/en/stable/topics/https-certificates/)), então `pip install` pode funcionar onde o Requests falha. Para um bundle específico, passe `--cert` ou defina `PIP_CERT`. No macOS com o instalador do python.org, rode `Install Certificates.command` uma vez.

Se um site do qual você coleta dados deixa de enviar o intermediário, baixe-o da CA emissora, adicione-o ao arquivo combinado (foi assim que o nosso servidor que só enviava a folha passou) e avise o dono do site.

## Como resolver no Node.js e no npm?

A saída do npm traz o código do Node.js:

```text
npm error code UNABLE_TO_GET_ISSUER_CERT_LOCALLY
npm error errno UNABLE_TO_GET_ISSUER_CERT_LOCALLY
npm error request to https://localhost:47443/demo-pkg failed, reason: unable to get local issuer certificate
```

O Node.js acrescenta à lista embutida as raízes do arquivo indicado em `NODE_EXTRA_CA_CERTS` ([documentação do Node.js](https://nodejs.org/api/cli.html#node_extra_ca_certsfile)). O npm roda sobre o Node.js, então uma única variável corrige os dois:

```bash
export NODE_EXTRA_CA_CERTS="$PWD/corp-root.pem"
npm view demo-pkg version --registry https://localhost:47443/
```

O segundo comando mostrou `1.0.0` (no PowerShell: `$env:NODE_EXTRA_CA_CERTS = "C:\certs\corp-root.pem"`). O Node.js lê a variável só na inicialização, não de dentro de um script em execução.

Já a configuração `cafile` do próprio npm substitui a lista confiável. Com só a raiz da empresa dentro, a nossa requisição ao registry público falhou:

```text
npm error request to https://registry.npmjs.org/left-pad failed, reason: unable to get local issuer certificate
```

Se a raiz já está no repositório do sistema, o Node.js pode ler esse repositório: `--use-system-ca` chegou no Node.js 23.8.0 e `NODE_USE_SYSTEM_CA=1` no 24.6.0 e no 22.19.0, e os dois funcionaram com o npm no nosso teste (`NODE_OPTIONS=--use-system-ca`). Assistentes de programação com IA feitos sobre o Node.js leem as mesmas variáveis; a documentação do Claude Code indica `NODE_EXTRA_CA_CERTS` para CAs de empresa.

## Como resolver no Git?

Com o backend OpenSSL, o Git repassa o texto do curl:

```text
fatal: unable to access 'https://localhost:47443/repo.git/': SSL certificate OpenSSL verify result: unable to get local issuer certificate (20)
```

No Windows, deixe o Git usar o repositório do Windows. O instalador do Git for Windows oferece "Use the OpenSSL library" (usar a biblioteca OpenSSL) e "Use the native Windows Secure Channel library" (usar a biblioteca nativa Secure Channel do Windows); dá para trocar depois:

```bash
git config --global http.sslBackend schannel
```

Se a raiz também não estiver lá, o Schannel informa `SEC_E_UNTRUSTED_ROOT (0x80090325)` com uma mensagem no idioma do sistema; em inglês, ela diz "The certificate chain was issued by an authority that is not trusted" (a cadeia de certificados foi emitida por uma autoridade que não é confiável). O Schannel ignora `http.sslCAInfo`, a menos que `http.schannelUseSSLCAInfo` esteja definido.

No macOS, no Linux ou com o backend OpenSSL, aponte o Git para um arquivo só para um servidor:

```bash
git config --global http.https://git.example.com/.sslCAInfo /path/to/corp-root.pem
```

No nosso teste, o host configurado passou pelo TLS, outro host continuou falhando e `github.com` seguiu funcionando. Se o gateway inspeciona todos os sites, use o Schannel ou um arquivo combinado (o bundle do Git mais a raiz da empresa).

## Como resolver no curl?

O número do erro no curl é 60. O nosso curl 8.21.0 escreve a mensagem como abaixo; outros builds mostram `SSL certificate problem: unable to get local issuer certificate`:

```text
curl: (60) SSL certificate OpenSSL verify result: unable to get local issuer certificate (20)
More details here: https://curl.se/docs/sslcerts.html
```

- **`--cacert corp-root.pem`** verifica com esse arquivo e substitui o bundle padrão, então use um arquivo combinado se também acessar sites públicos.
- **`--ca-native`** (curl 8.2.0+) acrescenta o repositório do sistema aos builds com OpenSSL no Windows, e no macOS quando o curl foi compilado com o SecTrust da Apple.
- **O `curl.exe` do próprio Windows** usa o Schannel e o repositório do Windows. Com uma CA privada que não publica dados de revogação, ele para em `schannel: the revocation status is unknown`; `--ssl-revoke-best-effort` aceita isso e continua verificando a cadeia.
- **`--proxy-cacert`** verifica um proxy HTTPS, ou seja, um proxy acessado por uma URL de proxy `https://`. Um proxy `http://` comum não tem certificado próprio; [cURL com proxy: como configurar proxy HTTP e SOCKS5](/pt-br/blog/curl-proxy) cobre os dois casos.

A página do projeto curl sobre [verificação de certificados SSL](https://curl.se/docs/sslcerts.html) recomenda com ênfase nunca pular a verificação em produção.

## Por que verify=False, -k e NODE_TLS_REJECT_UNAUTHORIZED=0 não são soluções?

Essas opções param de verificar a cadeia em vez de consertá-la, então o cliente aceita qualquer certificado para qualquer nome, inclusive o de alguém na mesma rede que queira ler o seu tráfego; a documentação do Node.js chama `NODE_TLS_REJECT_UNAUTHORIZED=0` de inseguro com todas as letras. No nosso teste com o Git, `http.sslVerify=false` chegou ao servidor sem nenhum aviso, então a configuração quebrada simplesmente continua lá. Essas opções também se espalham: uma variável no perfil do shell chega a todo programa Node.js, e o `verify=False` de um teste acaba em produção.

## Como enviar uma cadeia completa do seu próprio servidor?

Configure o certificado folha seguido de todos os intermediários e deixe a raiz de fora. No nginx, o arquivo de `ssl_certificate` traz primeiro o certificado do servidor e depois os intermediários ([documentação do nginx](https://nginx.org/en/docs/http/configuring_https_servers.html#chains)); na ordem errada, o nginx se recusa a iniciar com `key values mismatch`. O Certbot grava `fullchain.pem` para isso, enquanto `cert.pem` contém só a folha; o Apache 2.4.8 e posteriores aceitam `fullchain.pem` em `SSLCertificateFile`. Confira de fora com `openssl s_client -connect yourdomain:443 -servername yourdomain -showcerts`: você deve ver as entradas `0` e `1` e `Verify return code: 0 (ok)`.

Duas mudanças de 2026 fazem valer a pena automatizar essa verificação. Desde 15 de março de 2026, os Baseline Requirements do CA/Browser Forum limitam os certificados públicos a 200 dias (100 a partir de março de 2027, 47 a partir de março de 2029), então as renovações ficam mais frequentes, e cada uma é uma chance de publicar o arquivo errado. Em 27 de maio de 2026, o Let's Encrypt passou o perfil padrão para os intermediários Generation Y, que chegam às conhecidas raízes ISRG por meio de assinaturas cruzadas; a Certify The Web, um cliente de certificados para Windows, documenta servidores Windows que enviam a cadeia mais curta até as novas raízes, que clientes mais antigos não conseguem verificar.

## Um proxy causa esse erro?

Só um proxy que descriptografa o tráfego. Em uma requisição `https://` por um proxy HTTP, o cliente envia `CONNECT host:443` e o proxy repassa bytes criptografados, então você verifica o certificado do próprio site, como o nosso túnel comum mostrou. Os gateways da Proxynet também são túneis comuns: uma conexão pelos [Proxies HTTPS](https://proxynet.io/pt-br/https-proxy) não precisa de nenhum certificado raiz no seu computador.

Quando você faz scraping com os [Proxies residenciais](https://proxynet.io/pt-br/residential-proxy), o endereço de saída muda, mas o certificado do site não, então um erro de cadeia em um alvo acompanha você em todas as saídas; trocar de IP não resolve.

## Onde você encontra esse erro

- **Instalação de pacotes em notebook da empresa:** npm, pip e yarn falham atrás de um gateway com inspeção TLS enquanto o navegador funciona.
- **Runners de CI e builds Docker:** um contêiner tem o próprio repositório de confiança, então a raiz da empresa precisa entrar na imagem.
- **Assistentes de programação com IA:** ferramentas de linha de comando feitas sobre o Node.js chamam as APIs pelo mesmo gateway.
- **Clientes de API:** no Postman, abra **Settings** (Configurações), depois **Certificates** (Certificados), ative **CA certificates** (certificados de CA) e selecione o arquivo PEM.
- **Scrapers:** um site sem o intermediário falha em todo script, mas abre no navegador.
- **Serviços internos:** painéis e servidores Git assinados por uma CA da empresa.

## Erros comuns

- **Apontar `REQUESTS_CA_BUNDLE`, o `cafile` do npm ou `--cacert` só para a raiz da empresa.** Os três substituem a lista padrão.
- **Adicionar o certificado errado.** O arquivo precisa conter a raiz que a TI publica, não o certificado folha de um site.
- **Salvar o certificado em DER binário.** Essas configurações esperam PEM, o formato de texto que começa com `-----BEGIN CERTIFICATE-----`.
- **Definir `NODE_EXTRA_CA_CERTS` dentro do script.** O Node.js só lê a variável na inicialização.
- **Publicar `cert.pem` em vez de `fullchain.pem`.** Os navegadores podem continuar funcionando, então ninguém percebe.

## Guia de decisão

| O que você vê | O que fazer |
|---|---|
| Só a entrada `0`, emissor público | O dono do site deve servir a cadeia completa; enquanto isso, adicione o intermediário ao seu bundle |
| O emissor é a sua empresa, o antivírus ou uma ferramenta de depuração | Peça essa raiz à TI e adicione-a em cada ferramenta |
| Cadeia pública e `0 (ok)`, uma ferramenta falha | Faça a ferramenta ler o repositório do sistema: truststore, `--use-system-ca`, Schannel |
| npm falha | `NODE_EXTRA_CA_CERTS`; `cafile` só com um bundle completo |
| Git falha em um servidor interno | `http.<url>.sslCAInfo` para esse host |
| curl falha | `--cacert` com um arquivo combinado, ou `--ca-native` |

## Perguntas frequentes

### Por que o site abre no navegador, mas falha no Python, no curl ou no Node.js?

O navegador e a ferramenta leem repositórios de confiança diferentes, e os navegadores completam sozinhos as cadeias incompletas. Um script lê o próprio bundle e não faz nenhuma das duas coisas; compare a cadeia com `openssl s_client` para ver qual é o seu caso.

### É seguro usar verify=False, -k ou NODE_TLS_REJECT_UNAUTHORIZED=0?

Não como correção. Essas opções desligam toda verificação de certificado, então qualquer um entre você e o servidor pode ler ou alterar o tráfego. Use-as no máximo para um único comando contra o seu próprio servidor de teste.

### Qual é a diferença entre "unable to get local issuer certificate" e "self-signed certificate in certificate chain"?

As duas significam que a cadeia termina em uma raiz em que a sua ferramenta não confia. Na primeira, a raiz não foi enviada e não foi encontrada localmente; na segunda, o servidor ou um proxy enviou a própria raiz. A correção é a mesma.

### Onde consigo o certificado raiz da minha empresa?

Com a equipe de TI ou de segurança; em um notebook gerenciado, ele já está no repositório do sistema, então truststore, `--use-system-ca` e Schannel funcionam sem arquivo. Nunca pegue uma raiz de uma fonte não oficial: uma raiz confiável pode assinar para qualquer domínio.

### Por que o pip funciona quando o Requests falha na mesma máquina?

Desde a versão 24.2, o pip verifica os certificados do sistema além do certifi, enquanto o Requests lê só o certifi. O pacote truststore dá ao seu script o mesmo comportamento.

### Um proxy pode causar "unable to get local issuer certificate"?

Só um que descriptografa o tráfego: um gateway de empresa, um antivírus com verificação de HTTPS ou um proxy de depuração. Um proxy de túnel que usa `CONNECT` repassa o certificado do próprio site sem alterações.

## Em resumo

O erro significa que o seu cliente não conseguiu ligar o certificado do servidor a uma raiz em que confia. O `openssl s_client` mostra o porquê: uma folha sozinha aponta para o servidor, um emissor da empresa para a inspeção TLS, e uma cadeia pública limpa para o bundle próprio da sua ferramenta. Adicione a raiz certa onde essa ferramenta procura e deixe a verificação ligada. Para proxies que tunelam o tráfego sem tocar nos certificados, veja a nossa página de [serviços de proxy](/pt-br/proxy).
