O erro MQTT "ssl certificate verify failed" significa que o Python recusou o certificado do broker durante o handshake TLS. É um recurso de segurança fazendo exatamente o seu trabalho, e a solução quase nunca é desligá-lo. Este guia mostra como ler o motivo exato na mensagem de erro e corrigi-lo corretamente com o paho-mqtt no Windows, macOS, Linux e Docker, usando broker.connectmq.com na porta 8883 como referência que sabidamente funciona.
Lendo a mensagem de erro
A exceção completa normalmente tem esta cara:
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed:
unable to get local issuer certificate (_ssl.c:1000)
O que importa é a parte depois dos dois-pontos. As variantes mais comuns apontam para causas diferentes:
| Detalhe da mensagem | Significado | Correção mais provável |
|---|---|---|
unable to get local issuer certificate |
O Python não encontra a CA raiz que assinou a cadeia | Corrigir ou fornecer o bundle de CAs |
self-signed certificate in certificate chain |
Algo apresentou um certificado assinado por uma CA privada não confiável | Inspeção por proxy ou antivírus, ou uma CA privada do broker que você não configurou |
Hostname mismatch, certificate is not valid for '...' |
O nome ao qual você se conectou não consta no certificado | Conectar usando o hostname correto, não um IP |
certificate has expired |
O relógio está errado ou o certificado do servidor expirou | Acertar a hora do sistema ou renovar o certificado do servidor |
Passo 1: confirme que o Python consegue verificar o broker
Este script curto testa o TLS sem MQTT, usando exatamente as configurações que o paho-mqtt usa por padrão:
import socket
import ssl
HOST, PORT = "broker.connectmq.com", 8883
ctx = ssl.create_default_context()
print("OpenSSL:", ssl.OPENSSL_VERSION)
print("Default verify paths:", ssl.get_default_verify_paths())
with socket.create_connection((HOST, PORT), timeout=10) as sock:
with ctx.wrap_socket(sock, server_hostname=HOST) as tls:
cert = tls.getpeercert()
print("Connected with", tls.version())
print("Issuer:", dict(x[0] for x in cert["issuer"]))
print("Valid until:", cert["notAfter"])
Se este script falhar com o mesmo erro, o problema está no repositório de confiança do ambiente Python, no relógio ou na rede. Se ele funcionar, mas o seu código MQTT falhar, o problema está na configuração do cliente MQTT: um caminho de ca_certs errado, conexão por IP ou um broker com certificado privado.
Passo 2: use uma configuração TLS correta no paho-mqtt
Com o paho-mqtt 2.x, esta é a base que funciona com qualquer broker que tenha um certificado publicamente confiável, incluindo o ConnectMQ:
import ssl
import time
import certifi
import paho.mqtt.client as mqtt
BROKER = "broker.connectmq.com"
TOPIC = "connectmq/demo/a8d2/verify-test"
def on_connect(client, userdata, flags, reason_code, properties):
print("CONNACK:", reason_code)
client.subscribe(TOPIC)
client.publish(TOPIC, "certificate verified")
def on_message(client, userdata, msg):
print("Received:", msg.payload.decode())
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="py-verify-a8d2")
client.on_connect = on_connect
client.on_message = on_message
# o certifi traz o bundle de CAs da Mozilla, independente do repositório do SO
client.tls_set(ca_certs=certifi.where(), cert_reqs=ssl.CERT_REQUIRED)
# Broker privado ConnectMQ:
# client.username_pw_set("YOUR_USERNAME", "YOUR_PASSWORD")
client.connect(BROKER, 8883, keepalive=60)
client.loop_start()
time.sleep(3)
client.loop_stop()
client.disconnect()
Instale as dependências com pip install "paho-mqtt>=2.0" certifi. Passar certifi.where() deixa o comportamento idêntico em todos os sistemas operacionais, o que muitas vezes é a correção definitiva mais rápida. Mantenha o certifi atualizado, porque as listas de CAs mudam com o tempo.
Correções para cada plataforma
macOS
Os instaladores do Python baixados de python.org incluem o próprio OpenSSL e não usam o Keychain do macOS. Até você executar o script que vem junto, eles simplesmente não têm nenhum certificado de CA, o que gera unable to get local issuer certificate. Resolva executando o script de certificados do instalador:
/Applications/Python\ 3.12/Install\ Certificates.command
Ajuste o número da versão de acordo com a sua instalação. O Python do Homebrew usa o bundle de CAs do Homebrew e normalmente funciona de primeira; se não funcionar, execute brew reinstall ca-certificates. Usar certifi.where() explicitamente evita o problema em qualquer caso.
Windows
No Windows, o Python carrega os certificados do repositório de certificados do Windows. As falhas costumam ter uma de duas causas:
- Um repositório de raízes desatualizado em uma máquina que não recebeu atualizações, de modo que falta uma raiz mais nova. Instale as atualizações do Windows ou use o certifi.
- Inspeção TLS feita por antivírus ou proxy corporativo, que gera
self-signed certificate in certificate chain. Confira o emissor exibido pelo script do Passo 1; se ele mencionar um produto de segurança, é esse produto que está interceptando a conexão.
Linux
O Python usa os caminhos padrão do OpenSSL, geralmente /etc/ssl/certs/ca-certificates.crt no Debian e Ubuntu ou /etc/pki/tls/certs/ca-bundle.crt em sistemas baseados em Red Hat. Se o pacote estiver ausente ou desatualizado:
# Debian / Ubuntu / Raspberry Pi OS
sudo apt update && sudo apt install --reinstall ca-certificates
sudo update-ca-certificates
# Fedora / RHEL
sudo dnf reinstall ca-certificates
Docker e imagens slim
Imagens como python:3.12-slim normalmente incluem certificados de CA, mas as variantes alpine e distroless podem não incluir. Adicione o pacote explicitamente:
FROM python:3.12-alpine
RUN apk add --no-cache ca-certificates
Ou instale o certifi na imagem e faça referência a ele no código. Você também pode apontar o OpenSSL para um bundle com a variável de ambiente SSL_CERT_FILE.
A variante hostname mismatch
Hostname mismatch aparece quando você chama client.connect("203.0.113.10", 8883) ou usa um alias interno que não consta no certificado. O TLS verifica se o nome solicitado aparece nos Subject Alternative Names do certificado. Conecte-se usando o hostname real do broker. No ConnectMQ, ele é broker.connectmq.com. Se precisar forçar um IP específico para testes, coloque-o no arquivo hosts em vez de se conectar pelo IP.
Brokers privados ou autoassinados
Se você roda seu próprio broker com uma CA privada, o bundle de CAs públicas nunca vai confiar nele. Passe a CA privada explicitamente com client.tls_set(ca_certs="my-ca.crt") e garanta que o certificado do broker inclua o hostname correto no SAN. O processo e o trabalho de manutenção que ele exige estão explicados em certificados autoassinados para MQTT.
Inspecionando a cadeia com o OpenSSL
Quando a saída do Python não basta, o OpenSSL mostra a cadeia exata que o servidor envia e onde a verificação para:
openssl s_client -connect broker.connectmq.com:8883 -servername broker.connectmq.com -showcerts </dev/null
Leia a seção Certificate chain de cima para baixo: a entrada 0 é o certificado do broker, as seguintes são intermediárias, e cada linha i: (emissor) deve corresponder à linha s: (sujeito) da próxima entrada. O Verify return code final informa se o repositório de confiança do próprio OpenSSL aceita a cadeia. Se o OpenSSL disser 0 (ok) mas o Python falhar, o Python está usando um repositório de confiança diferente do OpenSSL; imprimir ssl.get_default_verify_paths(), como no Passo 1, geralmente revela isso. Se o emissor da entrada 0 mencionar o fabricante do seu antivírus ou firewall, você está vendo uma inspeção TLS, e não o certificado real do broker.
Para comparar especificamente com o certifi, adicione -CAfile com o caminho exibido por python -c "import certifi; print(certifi.where())". Sucesso com o certifi e falha sem ele confirmam que o problema está no repositório do sistema operacional.
O que não fazer
Resultados de busca costumam sugerir estas linhas:
client.tls_set(cert_reqs=ssl.CERT_NONE)
client.tls_insecure_set(True)
Elas fazem o erro sumir desativando a autenticação do servidor. Suas credenciais passam a ir para qualquer servidor que responder, inclusive o de um atacante. Use-as, se for usar, apenas por alguns minutos de depuração local contra um broker de laboratório, nunca em código que vai para o repositório. O artigo de boas práticas de segurança MQTT explica por que autenticar o broker importa tanto quanto a criptografia.
Checklist de correção rápida
- Leia o detalhe depois de
certificate verify failed. - Execute o script do Passo 1 no mesmo ambiente.
- Use
certifi.where()comoca_certsou repare o repositório do SO. - Conecte-se pelo hostname, não pelo IP.
- Verifique
date -upara descartar problemas de relógio. - Olhe o emissor do certificado para detectar proxies.
- Para brokers privados, forneça seu próprio arquivo de CA.
Se continuar travado depois de resolver o handshake, nosso guia sobre como corrigir falhas de handshake TLS no MQTT cobre portas, SNI e versões de protocolo, e o tutorial de MQTT com TLS em Python mostra padrões completos para produção.
Por que certificados públicos economizam tempo
A maioria das falhas de verificação de certificado desaparece quando o broker usa um certificado de uma CA publicamente confiável e o renova automaticamente. É assim que o ConnectMQ funciona: um broker MQTT gerenciado com certificado Let's Encrypt nas portas 8883 e 8084, de modo que instalações padrão do Python o verificam sem arquivos extras. Você pode reproduzir todos os testes deste artigo no servidor MQTT público (gratuito), feito para testes e não para dados de produção, antes de passar para credenciais privadas.
Comece a usar o ConnectMQ
Cadastre-se em app.connectmq.com, escolha o plano Serverless gratuito, sem cartão de crédito, crie um broker e execute o exemplo de paho-mqtt acima com o username, a senha e o client ID gerados para você.
Perguntas frequentes
Por que o mesmo código funciona no Linux, mas falha no macOS?
A versão do Python para macOS de python.org vem sem certificados de CA até você executar o "Install Certificates.command". O Python no Linux usa o bundle do sistema, que normalmente já está instalado.
É seguro usar o certifi em vez do repositório do sistema operacional?
Sim. O certifi empacota a lista de raízes curada pela Mozilla. Basta mantê-lo atualizado com o pip, lembrando que ele não inclui CAs privadas ou corporativas.
Esse erro significa que o broker foi comprometido?
Raramente. Bundles de CA ausentes, hostnames errados, problemas de relógio e inspeção TLS são muito mais comuns. Verifique o nome do emissor para ter certeza.
Preciso do arquivo de certificado do ConnectMQ?
Não. O ConnectMQ usa um certificado Let's Encrypt publicamente confiável, então um bundle de CAs padrão, como o certifi ou o repositório do SO, é suficiente.



