Para conectar o paho-mqtt via TLS, chame client.tls_set() antes de connect() e use a porta TLS do broker, normalmente 8883. Sem argumentos, o paho 2.x verifica o certificado do broker contra as CAs confiáveis do seu sistema e checa o hostname, exatamente o que você quer para um broker com certificado público. Para uma CA privada você passa ca_certs=, e para TLS mútuo adiciona certfile= e keyfile=.
Este guia cobre cada opção do tls_set(), um exemplo completo de TLS mútuo e os erros SSL exatos que o paho lança, com como confirmar e corrigir cada um.
A conexão TLS mínima
import os
import time
import paho.mqtt.client as mqtt
def on_connect(client, userdata, flags, reason_code, properties):
print("CONNACK:", reason_code)
client.subscribe("testmqtt/tls-demo/#")
def on_message(client, userdata, msg):
print(msg.topic, msg.payload.decode())
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="tls-demo-1")
client.username_pw_set(os.environ["MQTT_USER"], os.environ["MQTT_PASS"])
client.tls_set() # CAs do sistema, TLS 1.2+, verificação de hostname ativa
client.on_connect = on_connect
client.on_message = on_message
client.connect("broker.connectmq.com", 8883, keepalive=60)
client.loop_start()
client.publish("testmqtt/tls-demo/hello", "encrypted hello", qos=1)
time.sleep(2)
client.loop_stop()
client.disconnect()Duas regras evitam a maioria dos problemas:
- Conecte usando o hostname que está no certificado, não um endereço IP. O paho envia o host passado em
connect()como SNI e o compara com o certificado. - Combine porta e protocolo. TLS vai para a 8883 (ou 8084 para WebSockets seguros); TCP sem criptografia vai para a 1883. Misturar os dois gera erros confusos, explicados abaixo. Veja portas MQTT para a lista completa.
As opções do tls_set()
| Argumento | Padrão no paho 2.x | Use quando |
|---|---|---|
ca_certs | None → trust store do SO | O broker usa uma CA privada ou autoassinada: caminho para o PEM da CA |
certfile | None | O broker exige certificados de cliente (TLS mútuo) |
keyfile | None | Chave privada do certfile, se não estiver no mesmo arquivo |
keyfile_password | None | A chave é criptografada; caso contrário o paho pode pedir no terminal |
cert_reqs | ssl.CERT_REQUIRED | Quase nunca mude; ssl.CERT_NONE desativa a verificação |
tls_version | ssl.PROTOCOL_TLS_CLIENT (negocia a versão mais alta) | Raramente; prefira definir uma versão mínima no seu próprio contexto |
ciphers | Padrões do OpenSSL | Você precisa seguir uma política de ciphers específica |
alpn_protocols | None | O endpoint roteia por ALPN, por exemplo MQTT na porta 443 |
Dois métodos relacionados completam o quadro:
tls_insecure_set(True)mantém a verificação da cadeia, mas pula a checagem do hostname. Precisa ser chamado depois detls_set(), senão o paho lançaValueError: Must configure SSL context before using tls_insecure_set.tls_set_context(ctx)aceita o seu própriossl.SSLContext. Use quando precisar de ajustes que otls_set()não expõe, e lembre que o TLS só pode ser configurado uma vez por cliente: uma segunda chamada lançaValueError: SSL/TLS has already been configured.
import ssl
ctx = ssl.create_default_context(cafile="/etc/mqtt/ca.crt") # verificação + hostname
ctx.minimum_version = ssl.TLSVersion.TLSv1_2
client.tls_set_context(ctx)CA privada ou broker autoassinado
Um broker Mosquitto na sua rede local geralmente tem um certificado assinado pela sua própria CA. Entregue ao paho o certificado da CA (não o certificado do broker, e nunca a chave privada):
client.tls_set(ca_certs="/etc/mqtt/ca.crt")
client.connect("mqtt.home.internal", 8883)O hostname ao qual você se conecta precisa aparecer no Subject Alternative Name do certificado do broker. Se você conecta por IP, o IP precisa estar no SAN como entrada IP:; uma entrada DNS: não cobre isso. Na dúvida, gere de novo o certificado do servidor com o SAN correto em vez de desligar as checagens. O passo a passo completo está em como corrigir erros de certificado autoassinado.
TLS mútuo com certificados de cliente
Com TLS mútuo o broker também verifica o cliente. Cada dispositivo recebe o próprio certificado, assinado por uma CA em que o broker confia, e muitos brokers conseguem mapear o CN do certificado para o usuário MQTT.
import ssl
import paho.mqtt.client as mqtt
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="device-0042")
client.tls_set(
ca_certs="/etc/mqtt/ca.crt", # CA que assinou o certificado do broker
certfile="/etc/mqtt/device-0042.crt", # certificado deste cliente (PEM)
keyfile="/etc/mqtt/device-0042.key", # sua chave privada (PEM)
keyfile_password=None, # ou leia de um cofre de segredos
cert_reqs=ssl.CERT_REQUIRED,
)
client.connect("mqtt.example.internal", 8883)
client.loop_forever()Pontos em que as pessoas tropeçam:
certfilepode conter o certificado do cliente seguido das CAs intermediárias, o que é necessário quando o broker só confia na raiz.- As chaves precisam estar em PEM. Converta um bundle PKCS#12 com
openssl pkcs12 -in device.p12 -out device.crt -clcerts -nokeyseopenssl pkcs12 -in device.p12 -out device.key -nocerts -nodes. - Proteja o arquivo de chave (
chmod 600) e nunca faça commit dele.
Se o seu broker suporta certificados de cliente, e como os mapeia para identidades, depende de cada broker. Os conceitos e a configuração no broker estão em certificados de cliente MQTT e mTLS.
Erros SSL comuns no paho e como corrigi-los
Com connect(), os erros TLS são lançados diretamente como exceções, o que facilita a leitura. Com connect_async() e loop_start(), a thread de rede continua tentando de novo, então chame client.enable_logger() para ver o erro. O sufixo (_ssl.c:NNNN) varia conforme a versão do Python; pesquise pelo texto que vem antes.
certificate verify failed: unable to get local issuer certificate
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1006)- Causa: o Python não encontra a CA que emitiu o certificado do broker. Ou o broker usa uma CA privada, ou o seu Python não tem bundle de CA (típico dos builds do python.org no macOS).
- Confirmar:
python3 -c "import ssl; print(ssl.get_default_verify_paths())"mostra onde o Python procura; oopenssl s_client(veja abaixo) mostra quem emitiu o certificado. - Correção: para uma CA privada, passe
ca_certs="ca.crt". Se falta o bundle, rode/Applications/Python 3.x/Install Certificates.commandno macOS, oupip install certifie usetls_set(ca_certs=certifi.where()).
self-signed certificate (in certificate chain)
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate in certificate chain (_ssl.c:1006)- Causa: a cadeia termina em uma raiz em que o seu cliente não confia. Builds antigos do OpenSSL escrevem
self signed certificate. - Correção: passe a CA raiz com
ca_certs=. Se o próprio certificado do broker é autoassinado (sem CA), passe esse arquivo comoca_certsou, melhor, emita-o a partir de uma pequena CA.
Hostname mismatch
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: Hostname mismatch, certificate is not valid for 'localhost'. (_ssl.c:1006)- Causa: o nome ou IP usado em
connect()não está na lista SAN do certificado. - Confirmar:
openssl s_client -connect host:8883 -servername host </dev/null 2>/dev/null | openssl x509 -noout -ext subjectAltName. - Correção: conecte com um nome que esteja no SAN ou reemita o certificado.
tls_insecure_set(True)esconde o problema e não deve ir para produção. Mais em hostname mismatch no MQTT.
certificate has expired
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: certificate has expired (_ssl.c:1006)- Causa: o certificado do broker (ou um intermediário) passou da data
notAfter, ou o relógio do dispositivo está errado. Dispositivos sem RTC que iniciam em 1970 caem no caso oposto:certificate is not yet valid. - Correção: renove o certificado; nos dispositivos, sincronize a hora (NTP) antes de conectar.
Porta errada: EOF ou wrong version number
ssl.SSLEOFError: [SSL: UNEXPECTED_EOF_WHILE_READING] EOF occurred in violation of protocol (_ssl.c:1006)
ssl.SSLError: [SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1006)- Causa: você chamou
tls_set()mas conectou em uma porta sem criptografia. Um listener MQTT geralmente só fecha a conexão (EOF ouConnectionResetError); uma porta que responde com HTTP ou outros bytes não TLS geraWRONG_VERSION_NUMBER. - Correção: use a porta TLS (8883). O erro inverso, sem
tls_set()contra a 8883, não gera nada útil: o broker derruba a conexão e você nunca recebe um CONNACK.
Certificado de cliente rejeitado (TLS mútuo)
ssl.SSLError: [SSL: TLSV13_ALERT_CERTIFICATE_REQUIRED] tlsv13 alert certificate required (_ssl.c:2580)
ssl.SSLError: [SSL: TLSV1_ALERT_UNKNOWN_CA] tlsv1 alert unknown ca (_ssl.c:1006)
ssl.SSLError: [SSL] PEM lib (_ssl.c:3900)certificate required: o broker exige um certificado de cliente e você não enviou nenhum; adicionecertfile/keyfile. Com TLS 1.3 esse alerta pode chegar logo depois do handshake, então pode aparecer como uma desconexão imediata em vez de uma exceção.unknown ca: o broker não confia na CA que assinou o seu certificado de cliente, ou você não enviou o intermediário. Corrija o arquivo de CA do broker ou acrescente o intermediário aocertfile.PEM libou[X509: KEY_VALUES_MISMATCH] key values mismatch: o certificado ou a chave não podem ser lidos, estão criptografados com outra senha, não são PEM ou a chave não pertence ao certificado. Para chaves RSA, compareopenssl x509 -noout -modulus -in device.crt | openssl md5comopenssl rsa -noout -modulus -in device.key | openssl md5.
Para uma lista de outros erros do paho, veja erros do paho-mqtt em Python.
Como testar a correção
Verifique primeiro o lado do broker, independentemente do Python:
openssl s_client -connect broker.connectmq.com:8883 \
-servername broker.connectmq.com -showcerts </dev/null
# procure: Verify return code: 0 (ok)Depois confirme que um cliente conhecido funciona com os mesmos arquivos:
mosquitto_sub -h broker.connectmq.com -p 8883 \
--capath /etc/ssl/certs \
-u "$MQTT_USER" -P "$MQTT_PASS" \
-t 'testmqtt/tls-demo/#' -v -C 1Por fim, rode seu script Python com logging ativado, para que qualquer problema restante seja impresso:
import logging
logging.basicConfig(level=logging.DEBUG)
client.enable_logger()Se o openssl funciona e o Python falha, a diferença quase sempre é o bundle de CA que o Python usa. Se os dois falham, o problema está no broker ou na rede. Há um checklist passo a passo do caminho inteiro em testar uma conexão MQTT com TLS, e você pode conferir o broker via WebSockets seguros com o cliente MQTT online.
TLS no paho se resume a uma chamada feita corretamente: tls_set() com a CA certa, a porta certa e um hostname que confere com o certificado. Mantenha a verificação ativa, corrija os certificados em vez de desligar checagens, e você terá criptografia e autenticação do broker sem código extra.
Perguntas frequentes
O que o tls_set() faz sem argumentos?
No paho-mqtt 2.x ele cria um contexto TLS de cliente com a versão de TLS mais alta suportada pelo Python, carrega os certificados CA confiáveis do sistema operacional, exige um certificado de servidor válido e verifica se o hostname confere. É a configuração certa para brokers com certificados publicamente confiáveis.
Qual a diferença entre tls_insecure_set(True) e cert_reqs=ssl.CERT_NONE?
tls_insecure_set(True) continua verificando a cadeia de certificados, mas pula a checagem do hostname. cert_reqs=ssl.CERT_NONE desativa a verificação por completo e aceita qualquer certificado. Os dois permitem ataques man-in-the-middle e só servem para testes locais rápidos.
Por que o paho diz certificate verify failed: unable to get local issuer certificate?
O Python não encontrou a CA que assinou o certificado do broker. Ou o broker usa uma CA privada (passe o arquivo dela com ca_certs=) ou o seu Python não tem bundle de CA, algo comum em instalações do python.org no macOS. Resolva rodando Install Certificates.command ou passando ca_certs=certifi.where().

