Para probar una conexión MQTT con TLS, revisa tres capas en orden: el handshake TLS en sí con openssl s_client, después una sesión MQTT real con mosquitto_sub o mosquitto_pub y, por último, la librería cliente que usan tus dispositivos. Cada capa puede fallar por motivos distintos, y probarlas por separado te dice exactamente cuál está rota.
Todos los ejemplos usan broker.connectmq.com en el puerto 8883 (MQTT sobre TLS). Sustituye el host por el de tu broker.
Paso 1: comprueba el handshake con openssl
openssl s_client habla TLS pero no MQTT, que es justo lo que quieres: muestra el certificado que envía el broker y si tu máquina confía en él, sin ninguna librería MQTT de por medio.
openssl s_client -connect broker.connectmq.com:8883 \
-servername broker.connectmq.com \
-verify_hostname broker.connectmq.com \
-showcerts </dev/nullQué hace cada opción:
-servernameenvía SNI (Server Name Indication). Los brokers y balanceadores que sirven varios nombres lo usan para elegir el certificado. Sin él puedes recibir un certificado por defecto y un error de hostname.-verify_hostnamehace que openssl compruebe que el certificado es válido para ese nombre, la misma comprobación que hace tu cliente MQTT.-showcertsimprime todos los certificados que envía el servidor, para ver si incluye el intermedio.</dev/nullcierra la conexión justo después del handshake en lugar de esperar entrada.
Al final de la salida, busca:
Verify return code: 0 (ok)Cualquier otra cosa es un problema que también verás en tu cliente MQTT:
| Resultado de openssl | Significado |
|---|---|
Verify return code: 20 (unable to get local issuer certificate) | La CA (o un intermedio que falta) no está en tu almacén de confianza |
Verify return code: 18 (self-signed certificate) | El certificado del servidor es autofirmado; pasa su CA con -CAfile |
Verify return code: 19 (self-signed certificate in certificate chain) | La cadena termina en una raíz privada en la que no confías |
Verify return code: 10 (certificate has expired) | Renueva el certificado en el broker |
Verify return code: 62 (hostname mismatch) | El certificado no incluye el nombre al que te conectaste |
wrong version number | Ese puerto es MQTT sin cifrar, no TLS (a menudo 1883) |
connect: Connection refused / timeout | Puerto cerrado o filtrado: no es un problema de TLS |
Para un broker con CA privada, añade -CAfile ca.crt y repite el comando. Si entonces devuelve 0 (ok), tus certificados están bien y el cliente solo necesita el mismo fichero de CA.
Inspecciona el certificado rápidamente
openssl s_client -connect broker.connectmq.com:8883 \
-servername broker.connectmq.com </dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates -ext subjectAltNameComprueba que subjectAltName contiene exactamente el hostname que usan tus dispositivos. Los clientes modernos ignoran el Common Name del certificado, así que un nombre que solo aparece en CN no cuenta. Para una comprobación en CI que falle dos semanas antes de la caducidad, pasa el certificado a openssl x509 -noout -checkend 1209600.
Para probar qué versiones de TLS acepta el broker, repite el primer comando con -tls1_2 o -tls1_3. Un dispositivo con una pila TLS antigua que solo habla TLS 1.2 necesita que el broker mantenga 1.2 activado; consulta TLS 1.2 frente a 1.3 en MQTT.
Paso 2: ejecuta una sesión MQTT real sobre TLS
Un handshake correcto no demuestra que el listener MQTT funcione detrás. Suscríbete y publica por 8883:
mosquitto_sub -h broker.connectmq.com -p 8883 \
--cafile /etc/ssl/certs/ca-certificates.crt \
-u "$MQTT_USER" -P "$MQTT_PASS" \
-t testmqtt/tls/check -v -dmosquitto_pub -h broker.connectmq.com -p 8883 \
--cafile /etc/ssl/certs/ca-certificates.crt \
-u "$MQTT_USER" -P "$MQTT_PASS" \
-t testmqtt/tls/check -m "hello over tls"Los clientes mosquitto solo activan TLS cuando les das una opción de CA. Usa --cafile con tu bundle de CAs (la ruta de arriba es la de Debian/Ubuntu; en macOS con Homebrew está en $(brew --prefix)/etc/ca-certificates/cert.pem), --capath /etc/ssl/certs o --tls-use-os-certs en mosquitto 2.x. Para una CA privada, apunta --cafile a tu ca.crt. Para TLS mutuo añade --cert client.crt --key client.key; consulta certificados de cliente y mTLS.
Cuando falla, mosquitto es escueto: imprime Error: A TLS error occurred. y nada más. Vuelve al paso 1: openssl te dice por qué. Si openssl funciona pero mosquitto falla, compara el hostname: -h debe ser el nombre del certificado, no una dirección IP.
Paso 3: prueba desde el código de tu cliente
Por último, prueba con la librería que usa tu aplicación. El almacén de certificados y los valores por defecto cambian según el lenguaje, así que una conexión que funciona con mosquitto puede fallar aquí.
import os
import paho.mqtt.client as mqtt
def on_connect(client, userdata, flags, reason_code, properties):
print("CONNACK:", reason_code)
client.publish("testmqtt/tls/check", "hello from python over tls", qos=1)
c = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, protocol=mqtt.MQTTv5)
c.tls_set() # CAs del sistema, verificación de hostname activa; ca_certs="ca.crt" para una CA privada
c.username_pw_set(os.environ["MQTT_USER"], os.environ["MQTT_PASS"])
c.on_connect = on_connect
c.connect("broker.connectmq.com", 8883)
c.loop_forever()import mqtt from "mqtt";
const client = mqtt.connect("mqtts://broker.connectmq.com:8883", {
username: process.env.MQTT_USER,
password: process.env.MQTT_PASS,
// ca: fs.readFileSync("ca.crt"), // solo para una CA privada
reconnectPeriod: 0,
});
client.on("connect", () => {
console.log("connected over TLS");
client.publish("testmqtt/tls/check", "hello from node over tls", () => client.end());
});
client.on("error", (err) => console.error(err.code, err.message));Los errores que producen son los que la gente busca:
| Librería | Error | Causa habitual |
|---|---|---|
| Python | [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate | Falta la CA o el intermedio |
| Python | certificate verify failed: Hostname mismatch, certificate is not valid for '...' | Conexión por IP o con un nombre incorrecto |
| Python | [SSL: WRONG_VERSION_NUMBER] wrong version number | Cliente TLS contra un puerto sin cifrar |
| Node.js | UNABLE_TO_VERIFY_LEAF_SIGNATURE — unable to verify the first certificate | El servidor no envía el intermedio |
| Node.js | SELF_SIGNED_CERT_IN_CHAIN | No se pasó la CA privada en ca |
| Node.js | ERR_TLS_CERT_ALTNAME_INVALID — Hostname/IP does not match certificate's altnames | El nombre no está en la lista SAN |
Las soluciones se explican una a una en certificate verify failed, fallo en el handshake TLS y hostname mismatch. El generador de código produce fragmentos de conexión con TLS para otros lenguajes.
Checklist rápida de pruebas TLS
openssl s_clientcon-servernamey-verify_hostnamedevuelveVerify return code: 0 (ok).-showcertsmuestra el certificado del servidor y el intermedio.- El SAN contiene exactamente el hostname que usan los clientes; el certificado no está a punto de caducar.
mosquitto_subymosquitto_pubintercambian un mensaje por8883con la verificación activada.- La librería de tu aplicación conecta con su almacén de confianza por defecto (o con la CA privada que distribuyes).
- El puerto sin cifrar
1883está cerrado o desactivado en producción si TLS es obligatorio. - Los dispositivos con pilas TLS antiguas conectan con la versión de TLS y los cipher suites que permites.
¿Quieres también una comprobación desde el navegador? El cliente MQTT online conecta por WebSockets seguros en el 8084, que en muchas configuraciones usa el mismo certificado: una forma rápida de confirmar el lado TLS del broker antes de depurar un dispositivo.
Probar TLS capa por capa convierte un vago "error de TLS" en una causa concreta en pocos minutos. Guarda el comando de openssl y la comprobación de caducidad en tus scripts, y ejecútalos cada vez que se renueve un certificado o cambie un balanceador.
Preguntas frecuentes
¿Cómo compruebo si un broker MQTT admite TLS?
Ejecuta openssl s_client -connect host:8883 -servername host. Si el handshake funciona, verás la cadena de certificados y 'Verify return code: 0 (ok)'. Un error 'wrong version number' significa que el puerto habla MQTT sin cifrar, no TLS.
¿Por qué TLS funciona en mi navegador pero falla en mi dispositivo?
Normalmente el servidor no envía su certificado intermedio. Los navegadores pueden descargar los intermedios que faltan; openssl, mosquitto y las pilas TLS embebidas no. Comprueba que s_client -showcerts muestra el intermedio y corrige el fichero de certificado del broker.
¿Es seguro usar --insecure o tls_insecure_set(True)?
Solo como diagnóstico temporal en tu propia máquina. Estas opciones desactivan la verificación del hostname, lo que deja la conexión expuesta a ataques man-in-the-middle. Corrige el certificado o el hostname y mantén la verificación activada.
