L'erreur MQTT « ssl certificate verify failed » signifie que Python a refusé le certificat du broker pendant le handshake TLS. C'est une fonction de sécurité qui fait son travail, et la solution n'est presque jamais de la désactiver. Ce guide montre comment lire la raison exacte dans le message d'erreur et la corriger proprement avec paho-mqtt sous Windows, macOS, Linux et Docker, en prenant broker.connectmq.com sur le port 8883 comme référence qui fonctionne.
Lire le message d'erreur
L'exception complète ressemble généralement à ceci :
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed:
unable to get local issuer certificate (_ssl.c:1000)
C'est la partie après les deux-points qui compte. Les variantes courantes renvoient à des causes différentes :
| Détail du message | Signification | Correction la plus probable |
|---|---|---|
unable to get local issuer certificate |
Python ne trouve pas l'AC racine qui a signé la chaîne | Réparer ou fournir le bundle d'AC |
self-signed certificate in certificate chain |
Quelque chose a présenté un certificat signé par une AC privée non reconnue | Inspection par un proxy ou un antivirus, ou AC privée du broker non configurée |
Hostname mismatch, certificate is not valid for '...' |
Le nom auquel vous vous êtes connecté ne figure pas sur le certificat | Se connecter avec le bon nom d'hôte, pas une IP |
certificate has expired |
L'horloge est fausse ou le certificat du serveur a expiré | Corriger l'heure système ou renouveler le certificat du serveur |
Étape 1 : vérifier que Python peut valider le broker
Ce court script teste TLS sans MQTT, avec exactement les réglages que paho-mqtt utilise par défaut :
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"])
Si ce script échoue avec la même erreur, le problème vient du magasin de confiance de l'environnement Python, de l'horloge ou du réseau. S'il réussit mais que votre code MQTT échoue, le problème se situe dans la configuration du client MQTT : mauvais chemin ca_certs, connexion par IP ou broker doté d'un certificat privé.
Étape 2 : utiliser une configuration TLS paho-mqtt correcte
Avec paho-mqtt 2.x, voici la base qui fonctionne avec tout broker doté d'un certificat reconnu publiquement, ConnectMQ compris :
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
# certifi fournit le bundle d'AC de Mozilla, indépendamment du magasin du système
client.tls_set(ca_certs=certifi.where(), cert_reqs=ssl.CERT_REQUIRED)
# Broker ConnectMQ privé :
# 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()
Installez les dépendances avec pip install "paho-mqtt>=2.0" certifi. Passer certifi.where() rend le comportement identique sur tous les systèmes d'exploitation, ce qui est souvent la correction durable la plus rapide. Tenez certifi à jour, car les listes d'AC évoluent avec le temps.
Corrections pour chaque plateforme
macOS
Les installateurs Python de python.org embarquent leur propre OpenSSL et n'utilisent pas le trousseau macOS. Tant que vous n'avez pas lancé le script fourni, ils n'ont aucun certificat d'AC, ce qui produit unable to get local issuer certificate. Corrigez cela en lançant le script de certificats de l'installateur :
/Applications/Python\ 3.12/Install\ Certificates.command
Adaptez le numéro de version à votre installation. Le Python de Homebrew utilise le bundle d'AC de Homebrew et fonctionne généralement d'emblée ; sinon, lancez brew reinstall ca-certificates. Utiliser explicitement certifi.where() évite le problème dans tous les cas.
Windows
Sous Windows, Python charge les certificats depuis le magasin de certificats Windows. Les échecs ont généralement l'une de ces deux causes :
- Un magasin de racines obsolète sur une machine qui n'a pas reçu de mises à jour, si bien qu'une racine plus récente manque. Installez les mises à jour Windows, ou utilisez certifi.
- Une inspection TLS par un antivirus ou un proxy d'entreprise, qui produit
self-signed certificate in certificate chain. Regardez l'émetteur affiché par le script de l'étape 1 ; s'il nomme un produit de sécurité, c'est ce produit qui intercepte la connexion.
Linux
Python utilise les chemins par défaut d'OpenSSL, généralement /etc/ssl/certs/ca-certificates.crt sur Debian et Ubuntu ou /etc/pki/tls/certs/ca-bundle.crt sur les systèmes dérivés de Red Hat. Si le paquet est absent ou obsolète :
# 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 et images allégées
Les images comme python:3.12-slim incluent généralement les certificats d'AC, mais les variantes alpine et distroless peuvent ne pas les avoir. Ajoutez le paquet explicitement :
FROM python:3.12-alpine
RUN apk add --no-cache ca-certificates
Vous pouvez aussi installer certifi dans l'image et le référencer dans le code, ou pointer OpenSSL vers un bundle avec la variable d'environnement SSL_CERT_FILE.
La variante Hostname mismatch
Hostname mismatch apparaît lorsque vous appelez client.connect("203.0.113.10", 8883) ou utilisez un alias interne absent du certificat. TLS vérifie que le nom demandé figure dans les Subject Alternative Names du certificat. Connectez-vous avec le vrai nom d'hôte du broker. Pour ConnectMQ, c'est broker.connectmq.com. Si vous devez forcer une IP précise pour un test, ajoutez-la dans votre fichier hosts plutôt que de vous connecter par IP.
Brokers privés ou auto-signés
Si vous exploitez votre propre broker avec une AC privée, le bundle d'AC publiques ne lui fera jamais confiance. Passez explicitement l'AC privée avec client.tls_set(ca_certs="my-ca.crt"), et assurez-vous que le certificat du broker inclut le bon nom d'hôte dans son SAN. Le processus et sa charge de maintenance sont expliqués dans les certificats auto-signés pour MQTT.
Inspecter la chaîne avec OpenSSL
Quand la sortie Python ne suffit pas, OpenSSL montre la chaîne exacte envoyée par le serveur et l'endroit où la vérification s'arrête :
openssl s_client -connect broker.connectmq.com:8883 -servername broker.connectmq.com -showcerts </dev/null
Lisez la section Certificate chain depuis le haut : l'entrée 0 est le certificat du broker, les entrées suivantes sont les intermédiaires, et chaque ligne i: (émetteur) doit correspondre à la ligne s: (sujet) de l'entrée suivante. Le Verify return code final indique si le magasin de confiance d'OpenSSL accepte la chaîne. Si OpenSSL affiche 0 (ok) mais que Python échoue, Python utilise un autre magasin de confiance qu'OpenSSL ; afficher ssl.get_default_verify_paths() comme à l'étape 1 le révèle généralement. Si l'émetteur de l'entrée 0 porte le nom de votre éditeur d'antivirus ou de pare-feu, vous êtes face à une inspection TLS et non au vrai certificat du broker.
Pour comparer spécifiquement avec certifi, ajoutez -CAfile avec le chemin affiché par python -c "import certifi; print(certifi.where())". Un succès avec certifi et un échec sans lui confirment que le problème vient du magasin du système d'exploitation.
Ce qu'il ne faut pas faire
Les résultats de recherche suggèrent souvent ces lignes :
client.tls_set(cert_reqs=ssl.CERT_NONE)
client.tls_insecure_set(True)
Elles font disparaître l'erreur en désactivant l'authentification du serveur. Vos identifiants partent alors vers n'importe quel serveur qui répond, y compris celui d'un attaquant. Utilisez-les, si jamais vous le faites, uniquement pendant quelques minutes de débogage local avec un broker de laboratoire, jamais dans du code qui sera commité. L'article sur les bonnes pratiques de sécurité MQTT explique pourquoi l'authentification du broker compte autant que le chiffrement.
Checklist de correction rapide
- Lisez le détail qui suit
certificate verify failed. - Lancez le script de l'étape 1 dans le même environnement.
- Utilisez
certifi.where()commeca_certs, ou réparez le magasin du système. - Connectez-vous par nom d'hôte, pas par IP.
- Vérifiez
date -upour détecter les problèmes d'horloge. - Examinez l'émetteur du certificat pour détecter les proxys.
- Pour les brokers privés, fournissez votre propre fichier d'AC.
Si vous êtes encore bloqué une fois le handshake corrigé, notre guide sur la résolution des échecs de handshake TLS MQTT couvre les ports, le SNI et les versions de protocole, et le tutoriel MQTT TLS en Python présente des modèles complets pour la production.
Pourquoi les certificats publics font gagner du temps
La plupart des échecs de vérification de certificat disparaissent lorsque le broker utilise un certificat d'une AC reconnue publiquement et le renouvelle automatiquement. C'est ainsi que fonctionne ConnectMQ : un broker MQTT managé avec un certificat Let's Encrypt sur les ports 8883 et 8084, que les installations Python standard vérifient sans fichier supplémentaire. Vous pouvez reproduire tous les tests de cet article sur le serveur MQTT public gratuit, réservé aux tests et aux prototypes, avant de passer à des identifiants privés.
Démarrer avec ConnectMQ
Inscrivez-vous sur app.connectmq.com, choisissez l'offre gratuite Serverless sans carte bancaire, créez un broker et lancez l'exemple paho-mqtt ci-dessus avec le nom d'utilisateur, le mot de passe et le client ID générés.
Questions fréquentes
Pourquoi le même code fonctionne-t-il sous Linux mais échoue-t-il sous macOS ?
La version macOS de python.org est livrée sans certificats d'AC tant que vous n'avez pas lancé « Install Certificates.command ». Le Python de Linux utilise le bundle du système, généralement déjà installé.
Est-il sûr d'utiliser certifi plutôt que le magasin du système d'exploitation ?
Oui. certifi empaquette la liste de racines sélectionnée par Mozilla. Tenez-le simplement à jour avec pip, et notez qu'il n'inclut pas les AC privées ou d'entreprise.
Cette erreur signifie-t-elle que le broker est compromis ?
Rarement. Les bundles d'AC manquants, les mauvais noms d'hôte, les problèmes d'horloge et l'inspection TLS sont bien plus fréquents. Vérifiez le nom de l'émetteur pour en avoir le cœur net.
Ai-je besoin du fichier de certificat de ConnectMQ ?
Non. ConnectMQ utilise un certificat Let's Encrypt reconnu publiquement, donc un bundle d'AC standard comme certifi ou le magasin du système suffit.



