Seguridad MQTT

Error SSL certificate verify failed en MQTT: cómo arreglar los clientes Python

¿Te aparece "[SSL: CERTIFICATE_VERIFY_FAILED]" cuando tu cliente MQTT en Python se conecta al puerto 8883? Así puedes encontrar la causa exacta y solucionarla correctamente en cada plataforma, sin desactivar la verificación.

Ilustración del error SSL certificate verify failed en MQTT con un terminal de Python que muestra una cadena de certificados rechazada en el puerto 8883
Pruébalo en tu propio broker MQTTConsigue un broker MQTT privado y gestionado con el plan Free y empieza con ejemplos de código listos para usar.
Pruébalo gratis Ejemplos de código Sin tarjeta

El error "ssl certificate verify failed" en MQTT significa que Python rechazó el certificado del broker durante el handshake TLS. Es una función de seguridad haciendo su trabajo, y la solución casi nunca es desactivarla. Esta guía muestra cómo leer la causa exacta en el mensaje de error y corregirla bien con paho-mqtt en Windows, macOS, Linux y Docker, usando broker.connectmq.com en el puerto 8883 como referencia que funciona.

Cómo leer el mensaje de error

La excepción completa suele tener este aspecto:

ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed:
unable to get local issuer certificate (_ssl.c:1000)

Lo que importa es la parte que sigue a los dos puntos. Las tres variantes habituales apuntan a causas distintas:

Detalle del mensaje Significado Solución más probable
unable to get local issuer certificate Python no encuentra la CA raíz que firmó la cadena Repara o proporciona el paquete de CA
self-signed certificate in certificate chain Algo presentó un certificado firmado por una CA privada no confiable Inspección por un proxy o antivirus, o una CA privada del broker que no has configurado
Hostname mismatch, certificate is not valid for '...' El nombre al que te conectaste no figura en el certificado Conéctate con el hostname correcto, no con una IP
certificate has expired El reloj está mal o el certificado del servidor ha caducado Corrige la hora del sistema o renueva el certificado del servidor

Paso 1: confirma que Python puede verificar el broker

Este breve script prueba TLS sin MQTT, con exactamente la misma configuración que paho-mqtt usa por defecto:

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 este script falla con el mismo error, el problema es el almacén de confianza del entorno de Python, el reloj o la red. Si funciona pero tu código MQTT falla, el problema está en la configuración del cliente MQTT: una ruta ca_certs incorrecta, una conexión por IP o un broker con un certificado privado.

Paso 2: usa una configuración TLS correcta en paho-mqtt

Con paho-mqtt 2.x, esta es la base que funciona con cualquier broker que tenga un certificado de confianza pública, incluido 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

# certifi incluye el paquete de CA de Mozilla, independiente del almacén del sistema operativo
client.tls_set(ca_certs=certifi.where(), cert_reqs=ssl.CERT_REQUIRED)

# Broker privado de 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()

Instala las dependencias con pip install "paho-mqtt>=2.0" certifi. Pasar certifi.where() hace que el comportamiento sea idéntico en todos los sistemas operativos, y a menudo es la solución permanente más rápida. Mantén certifi actualizado, porque las listas de CA cambian con el tiempo.

Soluciones para cada plataforma

macOS

Los instaladores de Python de python.org incluyen su propio OpenSSL y no usan el Llavero de macOS. Hasta que ejecutas el script incluido, no tienen ningún certificado de CA, lo que provoca unable to get local issuer certificate. Soluciónalo ejecutando el script de certificados del instalador:

/Applications/Python\ 3.12/Install\ Certificates.command

Ajusta el número de versión al de tu instalación. El Python de Homebrew usa el paquete de CA de Homebrew y normalmente funciona sin más; si no, ejecuta brew reinstall ca-certificates. Usar certifi.where() de forma explícita evita el problema en cualquier caso.

Windows

En Windows, Python carga los certificados desde el almacén de certificados de Windows. Los fallos suelen tener una de estas dos causas:

  • Un almacén de raíces desactualizado en una máquina que no ha recibido actualizaciones, de modo que falta una raíz más reciente. Instala las actualizaciones de Windows o usa certifi.
  • Inspección TLS por parte de un antivirus o un proxy corporativo, que produce self-signed certificate in certificate chain. Revisa el emisor que imprime el script del paso 1; si menciona un producto de seguridad, ese producto está interceptando la conexión.

Linux

Python usa las rutas por defecto de OpenSSL, normalmente /etc/ssl/certs/ca-certificates.crt en Debian y Ubuntu o /etc/pki/tls/certs/ca-bundle.crt en sistemas basados en Red Hat. Si el paquete falta o está obsoleto:

# 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 imágenes slim

Imágenes como python:3.12-slim suelen incluir certificados de CA, pero las variantes alpine y distroless pueden no hacerlo. Añade el paquete explícitamente:

FROM python:3.12-alpine
RUN apk add --no-cache ca-certificates

O instala certifi en la imagen y haz referencia a él en el código. También puedes apuntar OpenSSL a un paquete con la variable de entorno SSL_CERT_FILE.

La variante hostname mismatch

Hostname mismatch aparece cuando llamas a client.connect("203.0.113.10", 8883) o usas un alias interno que no figura en el certificado. TLS comprueba que el nombre que pediste aparece en los Subject Alternative Names del certificado. Conéctate con el hostname real del broker. En ConnectMQ es broker.connectmq.com. Si necesitas forzar una IP concreta para hacer pruebas, ponla en tu archivo hosts en lugar de conectarte por IP.

Brokers privados o autofirmados

Si operas tu propio broker con una CA privada, el paquete de CA públicas nunca confiará en él. Pasa la CA privada explícitamente con client.tls_set(ca_certs="my-ca.crt") y asegúrate de que el certificado del broker incluye el hostname correcto en su SAN. El proceso y la carga de mantenimiento que implica se explican en certificados autofirmados para MQTT.

Inspeccionar la cadena con OpenSSL

Cuando la salida de Python no basta, OpenSSL muestra la cadena exacta que envía el servidor y dónde se detiene la verificación:

openssl s_client -connect broker.connectmq.com:8883 -servername broker.connectmq.com -showcerts </dev/null

Lee la sección Certificate chain desde arriba: la entrada 0 es el certificado del broker, las siguientes son intermedios, y cada línea i: (emisor) debe coincidir con la línea s: (sujeto) de la entrada siguiente. El Verify return code final te dice si el almacén de confianza del propio OpenSSL acepta la cadena. Si OpenSSL dice 0 (ok) pero Python falla, Python está usando un almacén de confianza distinto del de OpenSSL; imprimir ssl.get_default_verify_paths() como en el paso 1 suele revelarlo. Si el emisor de la entrada 0 menciona al fabricante de tu antivirus o firewall, estás viendo una inspección TLS y no el certificado real del broker.

Para comparar específicamente con certifi, añade -CAfile con la ruta que imprime python -c "import certifi; print(certifi.where())". Un éxito con certifi y un fallo sin él confirman que el problema es el almacén del sistema operativo.

Lo que no debes hacer

Los resultados de búsqueda suelen sugerir estas líneas:

client.tls_set(cert_reqs=ssl.CERT_NONE)
client.tls_insecure_set(True)

Hacen desaparecer el error desactivando la autenticación del servidor. Tus credenciales irán entonces a cualquier servidor que responda, incluido el de un atacante. Úsalas, si acaso, solo durante unos minutos de depuración local contra un broker de laboratorio, nunca en código que se suba al repositorio. El artículo de buenas prácticas de seguridad MQTT explica por qué autenticar al broker importa tanto como el cifrado.

Checklist de solución rápida

  1. Lee el detalle que sigue a certificate verify failed.
  2. Ejecuta el script del paso 1 en el mismo entorno.
  3. Usa certifi.where() como ca_certs, o repara el almacén del sistema operativo.
  4. Conéctate por hostname, no por IP.
  5. Comprueba date -u para descartar problemas de reloj.
  6. Mira el emisor del certificado para detectar proxies.
  7. En brokers privados, proporciona tu propio archivo de CA.

Si sigues atascado después de arreglar el handshake, nuestra guía sobre cómo solucionar fallos del handshake TLS en MQTT cubre los puertos, SNI y las versiones del protocolo, y el tutorial de TLS en MQTT con Python muestra patrones completos para producción.

Por qué los certificados públicos ahorran tiempo

La mayoría de los fallos de verificación de certificados desaparecen cuando el broker usa un certificado de una CA de confianza pública y lo renueva automáticamente. Así funciona ConnectMQ: un broker MQTT gestionado con un certificado de Let's Encrypt en los puertos 8883 y 8084, de modo que las instalaciones estándar de Python lo verifican sin archivos adicionales. Puedes reproducir todas las pruebas de este artículo en el servidor MQTT público gratuito antes de pasar a credenciales privadas.

Empieza con ConnectMQ

Regístrate en app.connectmq.com, elige el plan gratuito Serverless sin tarjeta de crédito, crea un broker y ejecuta el ejemplo de paho-mqtt anterior con el usuario, la contraseña y el client ID generados.

Preguntas frecuentes

¿Por qué el mismo código funciona en Linux pero falla en macOS?

La compilación de python.org para macOS viene sin certificados de CA hasta que ejecutas "Install Certificates.command". El Python de Linux usa el paquete del sistema, que normalmente ya está instalado.

¿Es seguro usar certifi en lugar del almacén del sistema operativo?

Sí. certifi empaqueta la lista de raíces seleccionada por Mozilla. Solo tienes que mantenerlo actualizado con pip, y ten en cuenta que no incluirá CA privadas ni corporativas.

¿Significa este error que el broker está comprometido?

Rara vez. Los paquetes de CA ausentes, los hostnames incorrectos, los problemas de reloj y la inspección TLS son mucho más habituales. Revisa el nombre del emisor para asegurarte.

¿Necesito el archivo del certificado de ConnectMQ?

No. ConnectMQ usa un certificado de Let's Encrypt de confianza pública, así que basta con un paquete de CA estándar como certifi o el almacén del sistema operativo.

Artículos relacionados

Cómo rotar credenciales MQTT sin tiempo de inactividad

Una estrategia de doble credencial paso a paso para rotar usuarios y contraseñas MQTT en toda una flota: prepara la nueva credencial, cambia con respaldo, verifica y solo entonces revoca la antigua.

8 min de lectura

Listas de control de acceso (ACL) en MQTT: conceptos y diseño

Qué son las listas de control de acceso de MQTT, cómo expresan brokers como Mosquitto las reglas de lectura y escritura por topic, cómo se comportan las operaciones denegadas y cómo diseñar espacios de nombres de topics para que la autorización siga siendo sencilla.

8 min de lectura

Todos los artículos →

Tu broker MQTT privado, a unos pocos clics

Crea una cuenta, obtén las credenciales de tu broker y conecta tu primer dispositivo, sin servidores que instalar, parchear ni escalar.