Erros de certificado SSL: 'não seguro', inválido e handshake

O cliente não conseguiu confiar no certificado do servidor: ele venceu, não cobre o domínio, tem a cadeia incompleta ou vem de um emissor desconhecido. Rode openssl s_client para ver qual é.

Atualizado em 5 min de leitura

Objetivos de aprendizado

Depois de ler este artigo, você vai conseguir:

  • Identificar a causa pela mensagem de erro do navegador, do curl, do Node.js ou do Python
  • Diagnosticar certificado vencido, domínio errado e cadeia incompleta com openssl
  • Corrigir o problema sem desativar a verificação de certificado
Nesta página

Você abre o seu site e o navegador mostra uma tela vermelha ou o aviso "Não seguro". Ou o seu bot, a sua API ou um curl quebra com certificate has expired. Todos esses casos têm a mesma raiz: na hora de montar a conexão TLS, o cliente recebeu um certificado e não conseguiu confiar nele.

A boa notícia é que são poucas causas, e dá para achar a certa em um minuto com openssl. Este guia vai da mensagem de erro até a correção, sem pedir para você desligar a verificação. Se quiser entender o que o certificado faz, veja o que é SSL/TLS.

Mensagem de erronavegador, curl ou Node.jsconfiraDatanotAftercorrijarenove erecarregueDomíniosubjectAltNameemita comtodos os nomesCadeiaintermediáriossirva ofullchain.pem
Da mensagem de erro à causa: confira a data, o nome e a cadeia, e cada causa tem uma correção própria.

Como ler a mensagem de erro?#

A mensagem já aponta a causa. Chrome e navegadores baseados nele mostram um código NET::ERR_...:

text
NET::ERR_CERT_DATE_INVALID          # venceu, ou o relógio do dispositivo está errado
NET::ERR_CERT_COMMON_NAME_INVALID   # o certificado não cobre este domínio
NET::ERR_CERT_AUTHORITY_INVALID     # emissor desconhecido, autoassinado ou cadeia incompleta
ERR_SSL_VERSION_OR_CIPHER_MISMATCH  # handshake: sem versão de TLS ou cifra em comum
ERR_SSL_PROTOCOL_ERROR              # handshake: o servidor não falou TLS direito

Fora do navegador, os nomes vêm do OpenSSL. No Node.js, o erro traz err.code:

text
CERT_HAS_EXPIRED                    # venceu
CERT_NOT_YET_VALID                  # data de início no futuro
ERR_TLS_CERT_ALTNAME_INVALID        # nome do domínio não bate
UNABLE_TO_VERIFY_LEAF_SIGNATURE     # cadeia incompleta
DEPTH_ZERO_SELF_SIGNED_CERT         # autoassinado
SELF_SIGNED_CERT_IN_CHAIN           # autoassinado dentro da cadeia

O curl responde com curl: (60) SSL certificate problem: unable to get local issuer certificate, e o Python com ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED]. Esses dois são genéricos: dizem que a verificação falhou, e a causa vem do texto que segue ou do diagnóstico abaixo.

Como diagnosticar com openssl?#

Um comando mostra datas, emissor e nomes cobertos:

bash
echo | openssl s_client -connect exemplo.com:443 -servername exemplo.com 2>/dev/null \
  | openssl x509 -noout -dates -issuer -subject -ext subjectAltName

Leia a saída assim:

  • notAfter no passado: o certificado venceu.
  • subjectAltName sem o seu domínio: o certificado é de outro nome.
  • issuer igual ao subject: autoassinado.

Para ver a cadeia e o veredito da verificação, rode o s_client sem filtros e procure Verify return code. O valor 0 (ok) indica sucesso. Qualquer outro traz o motivo, como 20 (unable to get local issuer certificate).

O argumento -servername importa: muitos servidores hospedam vários domínios no mesmo IP e escolhem o certificado pelo nome enviado na extensão SNI. Sem ele, você pode receber o certificado errado e achar que o problema é outro.

Por que o certificado venceu?#

É a causa número um. Certificados do Let's Encrypt têm validade curta, e a renovação automática falha em silêncio quando o timer é desativado, a porta 80 passa a ser usada por outro serviço ou a credencial da API de DNS expira. O Let's Encrypt vem encurtando esse prazo em etapas, então a renovação manual deixa de ser viável.

Correção:

  1. Rode a renovação e leia o erro: sudo certbot renew --dry-run.
  2. Corrija a causa (porta 80 liberada, DNS apontando para o servidor certo, credencial renovada).
  3. Recarregue o servidor web, porque muitos só leem o certificado na inicialização: sudo systemctl reload nginx.
  4. Confirme com o comando openssl acima.

Detalhes do processo em como funciona o Let's Encrypt. Antes de culpar o servidor, veja a data do seu computador ou celular: relógio errado gera ERR_CERT_DATE_INVALID mesmo com certificado válido.

O que fazer quando o domínio não bate?#

Um certificado vale para uma lista de nomes. exemplo.com não cobre www.exemplo.com, a menos que ambos estejam na lista, e um certificado *.exemplo.com cobre api.exemplo.com, mas não exemplo.com nem a.b.exemplo.com. Causas frequentes:

  • O domínio aponta para um servidor que tem o certificado de outro site.
  • O DNS ainda aponta para o IP antigo (veja propagação de DNS).
  • Você emitiu o certificado só para o domínio raiz e esqueceu o www.

Corrija emitindo o certificado com todos os nomes usados e conferindo para onde o domínio resolve (dig +short exemplo.com). O passo a passo está em como apontar um domínio para sua aplicação.

E se a cadeia estiver incompleta?#

O servidor precisa enviar o certificado do site mais os intermediários. Navegadores às vezes completam a cadeia sozinhos, mas curl, Node.js e Python não, e é por isso que o site abre no Chrome e a sua API falha com UNABLE_TO_VERIFY_LEAF_SIGNATURE ou unable to get local issuer certificate.

A correção é servir o arquivo com a cadeia completa. Com o Certbot, é o fullchain.pem, não o cert.pem:

nginx
ssl_certificate     /etc/letsencrypt/live/exemplo.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/exemplo.com/privkey.pem;

Se o erro aparece só na sua máquina, com um certificado de uma CA privada (rede corporativa, proxy de inspeção, ambiente interno), confie nessa CA em vez de desligar a verificação:

bash
curl --cacert ./minha-ca.pem https://interno.exemplo.com
NODE_EXTRA_CA_CERTS=./minha-ca.pem node app.js

No Node.js, rejectUnauthorized vem como true por padrão, e false remove toda a proteção contra interceptação. Para uma CA própria, passe-a na opção ca.

Por que aparece "Não seguro" com HTTPS ativo?#

Se o certificado está certo e o cadeado ainda não aparece, o suspeito é conteúdo misto: a página veio por HTTPS, mas carrega algo por http://. Segundo o MDN, navegadores atualizam imagens, áudio e vídeo para HTTPS por conta própria, mas bloqueiam scripts, CSS, iframes e chamadas fetch. O resultado é uma página quebrada ou com aviso.

Abra o console do navegador (F12): cada recurso afetado aparece com a URL. Troque http:// por https:// ou por caminhos relativos. Como atalho, o cabeçalho Content-Security-Policy: upgrade-insecure-requests pede ao navegador que atualize todas as requisições, mas só funciona se o outro lado também atende por HTTPS.

E os erros de handshake?#

Se o erro é ERR_SSL_VERSION_OR_CIPHER_MISMATCH ou ERR_SSL_PROTOCOL_ERROR, a negociação falhou antes da verificação do certificado. Cheque:

  • Porta ou protocolo errado: você está falando HTTPS com uma porta que atende HTTP puro (ou o contrário).
  • Versão de TLS antiga: servidores só com TLS 1.0 ou 1.1, já descontinuados, são recusados por clientes atuais. Use TLS 1.2 ou 1.3.
  • Servidor sem certificado configurado para aquele nome ou porta.

Teste com openssl s_client -connect exemplo.com:443 -tls1_2 e -tls1_3 para ver qual versão o servidor aceita.

Como fazer isso na Square Cloud#

Na Square Cloud, várias dessas causas somem porque o TLS é automático: o certificado DV é emitido e renovado sozinho, e o HTTP é redirecionado para HTTPS. Toda aplicação web ganha um subdomínio *.squareweb.app com HTTPS, em todos os planos.

Para domínio próprio, a partir do plano Standard, você cria no seu registrador os registros DNS mostrados no painel e acompanha ali o status da validação em tempo real. Se a validação não conclui, confira se os registros criados são exatamente os que o painel mostra. O TLS do domínio também é automático, e há suporte a domínios com acento (Punycode) e purge de cache. O domínio continua registrado em seu nome no seu registrador.

Veja a página de domínios para o passo a passo.

Rede

Domínio próprio, TLS automático e CDN global para o seu app

Saiba mais

Perguntas frequentes

Por que o navegador diz 'Não seguro' se o site tem HTTPS?

Duas causas comuns: o certificado tem problema (vencido, domínio errado, emissor desconhecido) ou a página HTTPS carrega algum recurso por HTTP, o chamado conteúdo misto. O console do navegador mostra qual dos dois é.

Posso desativar a verificação de certificado para o erro sumir?

Só em teste local descartável. Com rejectUnauthorized: false no Node.js ou curl -k, qualquer pessoa no caminho pode se passar pelo servidor. Em produção, corrija o certificado ou instale a CA correta.

O que significa NET::ERR_CERT_DATE_INVALID?

O certificado venceu ou ainda não é válido. Se o certificado do servidor está dentro do prazo, confira o relógio do dispositivo: data errada gera o mesmo erro.

Qual a diferença entre erro de certificado e erro de handshake?

No erro de certificado, o handshake chegou até a verificação e o certificado foi recusado. No erro de handshake, a negociação falha antes: versão de TLS ou cifras sem ponto em comum, ou o servidor nem responde em TLS naquela porta.

Próximo artigoO que é CDN e como ela acelera seu siteCDN é uma rede de servidores espalhados pelo mundo que guarda cópias do seu conteúdo e responde perto do visitante. Veja como funciona, o que cachear e os limites.

Mais em Rede e domínios

Ver todos os 27 artigos