Abre no navegador, quebra no curl

O certificado não está errado — está incompleto. E o navegador esconde isso de você. Confira a cadeia elo a elo aqui.

Roda no seu navegador · nada é enviado para servidor

Cole o erro

Do curl, do Node, do Java, do Python, do Docker — ou o código de erro que o navegador mostrou.

Confira a cadeia elo a elo

Rode o comando abaixo e cole a saída inteira. Ele mostra os certificados que o servidor realmente entrega — que nem sempre são os que você instalou.

openssl s_client -connect seudominio.com:443 -servername seudominio.com < /dev/null 2>/dev/null

Por que funciona no navegador e falha em todo o resto

Esta é a pergunta que traz quase todo mundo aqui, e a resposta é uma diferença de comportamento que quase ninguém conhece.

Um certificado não é validado sozinho. Ele forma uma cadeia: o seu certificado foi assinado por uma autoridade intermediária, que foi assinada por uma raiz. O cliente já confia na raiz — ela vem instalada no sistema — mas precisa dos elos do meio para chegar até ela.

Quando o servidor esquece de enviar a intermediária, a cadeia fica com um buraco. E aí:

O navegador conserta sozinho. Chrome, Firefox e Safari leem um campo do certificado chamado AIA, que diz onde baixar a intermediária faltante, e vão buscar. Você nunca vê o problema.

O resto do mundo não faz isso. curl, Node, Java, Python, Go, Docker — nenhum busca a intermediária. Eles recebem a cadeia incompleta, não conseguem chegar até a raiz confiável, e recusam.

É por isso que “no meu navegador funciona” não vale como teste de certificado. O navegador é o cliente mais permissivo que existe nesse quesito — ele te esconde exatamente o erro que vai derrubar a sua integração.

O erro de instalação que causa tudo isso

A raiz do problema costuma ser um arquivo trocado. Toda autoridade entrega pelo menos dois arquivos, e os nomes enganam:

ArquivoO que tem dentroÉ o que o servidor precisa?
cert.pem só o seu certificado não — falta a intermediária
chain.pem só as intermediárias não — falta o seu
fullchain.pem o seu + as intermediárias sim, é este

No nginx é a diferença entre funcionar em todo lugar e funcionar só no navegador:

ssl_certificate     /etc/letsencrypt/live/dominio/fullchain.pem;   # ← não cert.pem
ssl_certificate_key /etc/letsencrypt/live/dominio/privkey.pem;

O Apache separa em duas diretivas, e a intermediária tem linha própria:

SSLCertificateFile      /caminho/cert.pem
SSLCertificateChainFile /caminho/chain.pem

A ordem importa

A cadeia é lida de baixo para cima e precisa vir na ordem: primeiro o seu certificado, depois quem o assinou, depois quem assinou aquele. Inverter quebra em vários clientes.

A raiz não precisa ser enviada — o cliente já tem. Mandar não costuma quebrar nada, só desperdiça alguns kilobytes em cada handshake.

O que nunca fazer

Toda pesquisa sobre erro de certificado devolve uma dessas linhas como primeira resposta. Todas elas desligam a verificação:

curl -k                              # ignora o certificado
NODE_TLS_REJECT_UNAUTHORIZED=0       # desliga a verificação no Node inteiro
rejectUnauthorized: false            # idem, por requisição
verify=False                         # requests, Python

TLS faz duas coisas: criptografa e autentica. Desligar a verificação mantém a primeira e joga a segunda fora. A conexão continua embaralhada, mas você deixa de ter qualquer garantia de estar falando com quem pensa que está — que é justamente o ataque que o certificado impede.

Serve para depurar por dois minutos. Em produção, é uma porta aberta que ninguém lembra de fechar.

Quando o problema é a máquina, não o servidor

Se o mesmo endereço funciona na sua máquina e falha só em um container, o certificado está bem — falta a lista de raízes confiáveis lá dentro. Imagem enxuta como alpine não vem com ela:

# Alpine
RUN apk add --no-cache ca-certificates

# Debian/Ubuntu
RUN apt-get update && apt-get install -y ca-certificates

O outro caso é o proxy corporativo, que intercepta o tráfego e reassina tudo com uma raiz própria. O sintoma é self signed certificate in certificate chain em qualquer site, inclusive nos grandes. Aí a saída certa é instalar a raiz da empresa como confiável — não desligar a verificação.

Relógio errado derruba certificado válido

Certificado tem data de início e de fim, e a validação compara com o relógio local. Máquina com data errada — container sem sincronia, VM que dormiu, dispositivo sem bateria — recusa certificado perfeitamente válido, dizendo que expirou ou que ainda não vale.

date                # confira antes de culpar o certificado
openssl s_client -connect dominio:443 < /dev/null 2>/dev/null \
  | openssl x509 -noout -dates

Se o certificado está atrás de um proxy reverso, vale ler como o TLS é terminado — e o diagnóstico de Nginx 502 cobre o caso do proxy_pass https que falha no handshake.

Newsletter

Receba os artigos novos por e-mail

Sem spam. Só o aviso quando sai um tutorial ou artigo novo.