Pruebas de brokers

Cómo probar una conexión MQTT con TLS (openssl, mosquitto, código)

Prueba MQTT con TLS en el puerto 8883: openssl s_client, cadena de certificados, SNI y verificación del hostname, mosquitto --cafile y errores comunes.

Actualizado · 9 min de lectura

Cómo probar una conexión MQTT con TLS (openssl, mosquitto, código)

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.

Comprobación TLS básica
openssl s_client -connect broker.connectmq.com:8883 \
  -servername broker.connectmq.com \
  -verify_hostname broker.connectmq.com \
  -showcerts </dev/null

Qué hace cada opción:

  • -servername enví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_hostname hace que openssl compruebe que el certificado es válido para ese nombre, la misma comprobación que hace tu cliente MQTT.
  • -showcerts imprime todos los certificados que envía el servidor, para ver si incluye el intermedio.
  • </dev/null cierra la conexión justo después del handshake en lugar de esperar entrada.

Al final de la salida, busca:

Resultado correcto
Verify return code: 0 (ok)

Cualquier otra cosa es un problema que también verás en tu cliente MQTT:

Resultado de opensslSignificado
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 numberEse puerto es MQTT sin cifrar, no TLS (a menudo 1883)
connect: Connection refused / timeoutPuerto 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

Subject, issuer, SAN y caducidad
openssl s_client -connect broker.connectmq.com:8883 \
  -servername broker.connectmq.com </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -issuer -dates -ext subjectAltName

Comprueba 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:

Terminal 1 — suscripción sobre TLS
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 -d
Terminal 2 — publicación sobre TLS
mosquitto_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í.

tls_check.py (paho-mqtt 2.x)
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()
tls-check.mjs (MQTT.js 5)
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íaErrorCausa habitual
Python[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificateFalta la CA o el intermedio
Pythoncertificate 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 numberCliente TLS contra un puerto sin cifrar
Node.jsUNABLE_TO_VERIFY_LEAF_SIGNATURE — unable to verify the first certificateEl servidor no envía el intermedio
Node.jsSELF_SIGNED_CERT_IN_CHAINNo se pasó la CA privada en ca
Node.jsERR_TLS_CERT_ALTNAME_INVALID — Hostname/IP does not match certificate's altnamesEl 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

  1. openssl s_client con -servername y -verify_hostname devuelve Verify return code: 0 (ok).
  2. -showcerts muestra el certificado del servidor y el intermedio.
  3. El SAN contiene exactamente el hostname que usan los clientes; el certificado no está a punto de caducar.
  4. mosquitto_sub y mosquitto_pub intercambian un mensaje por 8883 con la verificación activada.
  5. La librería de tu aplicación conecta con su almacén de confianza por defecto (o con la CA privada que distribuyes).
  6. El puerto sin cifrar 1883 está cerrado o desactivado en producción si TLS es obligatorio.
  7. 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.