Seguridad MQTT

Error en el handshake TLS de MQTT: cómo diagnosticarlo y solucionarlo

Una guía paso a paso para resolver fallos del handshake TLS en MQTT con openssl s_client: puertos erróneos, CA ausentes, problemas de SNI, desfase de reloj, versiones de TLS incompatibles y proxies que interceptan el tráfico.

Ilustración de resolución de un error en el handshake TLS de MQTT con un handshake roto entre cliente y broker y la salida de diagnóstico en un terminal
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

Cuando aparece un error en el handshake TLS de MQTT, la conexión muere antes incluso de que empiece MQTT, y la mayoría de las librerías cliente lo notifican con un mensaje poco útil. Esta guía te enseña a aislar la causa en minutos con openssl s_client y después repasa los seis problemas que hay detrás de casi todos los fallos: puerto erróneo, CA ausente, SNI, desfase de reloj, versión de TLS incompatible y proxies que interceptan el tráfico. Todas las pruebas se hacen contra broker.connectmq.com en el puerto 8883, así que puedes comparar un endpoint que funciona con el tuyo.

Qué ocurre en un handshake TLS

Antes de que tu cliente envíe el paquete CONNECT de MQTT, debe completar un handshake TLS con el broker:

  1. El cliente envía un ClientHello con las versiones de TLS admitidas, los cipher suites y el nombre del servidor (SNI).
  2. El broker responde con la versión y el cifrado elegidos, además de su cadena de certificados.
  3. El cliente verifica la cadena contra las raíces de confianza, comprueba las fechas de validez y compara el hostname.
  4. Ambos lados derivan las claves y pasan a tráfico cifrado.

Un fallo en cualquier paso cierra la conexión. Tu librería MQTT muestra entonces algo como A TLS error occurred (mosquitto), ssl.SSLError (Python), ECONNRESET o un error de certificado (Node.js), o state=-2 (PubSubClient en ESP32). Lo que necesitas es la causa subyacente, y OpenSSL la muestra directamente. Para la configuración de referencia correcta, consulta nuestra guía de configuración TLS en MQTT.

Paso 1: reproduce el handshake con openssl s_client

Ejecuta esto desde la misma máquina o red que el cliente que falla:

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

Un resultado correcto termina con líneas parecidas a:

Verify return code: 0 (ok)

y muestra el protocolo y el cifrado negociados. Si OpenSSL funciona pero tu aplicación falla, el problema está en la configuración de la aplicación (ruta de la CA, hostname, opciones de la librería). Si OpenSSL también falla, el problema es la red, el almacén de confianza o el reloj. Esa sola comparación reduce el espacio de búsqueda a la mitad.

Variantes útiles:

# Resumen breve del protocolo, el cifrado y la verificación
openssl s_client -connect broker.connectmq.com:8883 -servername broker.connectmq.com -brief </dev/null

# Verificar contra un archivo de CA concreto, como haría un dispositivo embebido
openssl s_client -connect broker.connectmq.com:8883 -servername broker.connectmq.com \
  -CAfile isrgrootx1.pem </dev/null

# Inspeccionar las fechas y los nombres del certificado
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

Error en el handshake TLS de MQTT: síntomas y causas

Síntoma Causa probable Solución
wrong version number o packet length too long Cliente TLS hablando con un puerto en claro (1883) Usa el puerto 8883 para TLS
La conexión se queda colgada y acaba agotando el tiempo Cliente MQTT en claro en el puerto 8883, o puerto bloqueado por un firewall Activa TLS en el cliente; comprueba la accesibilidad
unable to get local issuer certificate (código 20) El cliente no confía en la CA raíz Instala o apunta al paquete de CA correcto
self-signed certificate in certificate chain (código 19) Un proxy o antivirus intercepta y sustituye el certificado Evita la inspección o confía deliberadamente en la CA del proxy
certificate is not yet valid (código 9) El reloj del dispositivo va atrasado Sincroniza la hora con NTP antes de conectar
certificate has expired (código 10) El reloj va adelantado o el certificado del servidor ha caducado de verdad Revisa el reloj del dispositivo y las fechas del servidor
Hostname mismatch / IP address mismatch Conexión por IP o con un nombre incorrecto Conéctate con el hostname que figura en el certificado
no protocols available / unsupported protocol Cliente limitado a versiones de TLS que el servidor no ofrece Permite TLS 1.2 o superior en el cliente
alert handshake failure Ningún cipher suite común o falta de SNI Actualiza la librería TLS; envía SNI

Causa 1: puerto o esquema erróneos

Es el error más común. El puerto 1883 habla MQTT en claro; el 8883 espera TLS desde el primer byte. Si tu cliente inicia TLS contra el 1883, OpenSSL indica wrong version number porque la respuesta del servidor no es un registro TLS. Si un cliente en claro se conecta al 8883, el broker espera un ClientHello que nunca llega y la conexión acaba cerrándose.

Revisa también el esquema de la URL. En MQTT.js, mqtt:// significa en claro y mqtts:// significa TLS; en navegadores, usa wss://broker.connectmq.com:8084/mqtt. En Python, lo que activa TLS es llamar a tls_set(); cambiar solo el puerto no basta.

Para comprobar si el puerto es accesible:

# Linux/macOS
nc -vz broker.connectmq.com 8883

# Windows PowerShell
Test-NetConnection broker.connectmq.com -Port 8883

Las redes corporativas y de invitados suelen bloquear el 8883. Si está bloqueado, WebSocket seguro en el 8084 es una alternativa habitual; consulta MQTT sobre WebSocket.

Causa 2: certificado de CA ausente o incorrecto

El error de verificación 20 significa que el cliente no pudo construir una cadena hasta una raíz de confianza. Situaciones típicas:

  • Los clientes de mosquitto no usan el almacén del sistema salvo que pases --capath /etc/ssl/certs (Linux) o --cafile. En Windows, descarga el PEM de ISRG Root X1 y pásalo con --cafile.
  • Las imágenes Docker mínimas a menudo no incluyen el paquete ca-certificates.
  • Los dispositivos embebidos recibieron la raíz equivocada, un PEM truncado o el certificado hoja en lugar de la raíz.
  • Python en macOS instalado desde python.org necesita ejecutar una vez su instalador de certificados; los detalles están en SSL CERTIFICATE_VERIFY_FAILED en MQTT.

El certificado del broker de ConnectMQ lo emite Let's Encrypt, así que cualquier almacén de confianza del sistema actualizado ya incluye la raíz necesaria.

Causa 3: falta de SNI

Server Name Indication le dice al servidor qué hostname quieres. Los endpoints con balanceo de carga y multi-tenant pueden presentar un certificado por defecto, o rechazar el handshake, cuando falta SNI. La mayoría de las librerías envían SNI automáticamente cuando te conectas con un hostname. Los problemas aparecen cuando el código resuelve primero la dirección IP y se conecta a la IP, o cuando se usan pilas TLS muy antiguas. Pasa siempre el hostname a la librería cliente y, en las pruebas con OpenSSL, incluye siempre -servername.

Causa 4: desfase de reloj

Los certificados solo son válidos entre dos fechas. Una Raspberry Pi sin hora de red, un ESP32 que no ha terminado NTP o una máquina virtual restaurada desde una instantánea antigua pueden ir días o décadas desfasados. Compruébalo con date -u en Linux y, en los microcontroladores, espera a NTP antes de abrir la conexión TLS. La guía de certificate pinning en ESP32 incluye un bucle de espera de NTP listo para usar.

Causa 5: versión de TLS o cifrado incompatibles

Un firmware antiguo, runtimes de Java heredados o ajustes fijados en el código como ssl.PROTOCOL_TLSv1 de Python pueden ofrecer solo versiones que los servidores modernos han desactivado. Comprueba qué versiones acepta un servidor:

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

Configura tu cliente para que negocie automáticamente (por ejemplo, ssl.PROTOCOL_TLS_CLIENT en Python) en lugar de fijar una sola versión. Nuestro artículo sobre MQTT con TLS 1.3 explica con más detalle la elección de versiones y cifrados.

Causa 6: inspección TLS y proxies

Algunos firewalls corporativos y antivirus interceptan TLS presentando su propio certificado. El síntoma es el error de verificación 19 y un emisor con el nombre del producto de seguridad. Pide a tu equipo de red que excluya el hostname del broker o, si la inspección es obligatoria, confía explícitamente en la CA de la organización solo en ese entorno. Nunca distribuyas dispositivos con la verificación desactivada para esquivar un proxy.

Checklist sistemática

  1. Ejecuta openssl s_client con -servername desde el entorno que falla.
  2. Confirma que el puerto coincide con el transporte: 1883 en claro, 8883 TLS, 8084 WebSocket seguro.
  3. Confirma que el cliente se conecta por hostname, no por IP.
  4. Comprueba que el almacén de confianza o el archivo de CA contiene la raíz correcta.
  5. Revisa el reloj del sistema en UTC.
  6. Permite la negociación automática de la versión de TLS.
  7. Descarta proxies probando desde otra red, como el punto de acceso del teléfono.
  8. Solo cuando el handshake funcione, depura los errores a nivel MQTT, como credenciales incorrectas o un conflicto de client ID.

El último punto es importante: un handshake correcto seguido de una desconexión con un reason code en el CONNACK es un problema de autenticación o de protocolo, no de TLS.

Menos problemas de handshake con un broker MQTT gestionado

Muchos incidentes TLS en brokers autoalojados vienen del lado del servidor: certificados caducados, cadenas incompletas o un listener mal configurado tras una actualización. Un broker MQTT gestionado traslada esa responsabilidad al proveedor. ConnectMQ sirve TLS en el 8883 y WebSocket seguro en el 8084 con certificados de Let's Encrypt renovados automáticamente, así que si un handshake falla, sabes que debes mirar en el lado del cliente. Puedes probar cualquier cliente primero contra el servidor MQTT público gratuito (solo para pruebas, no para datos de producción), y nuestra guía de broker MQTT en la nube explica cómo encajan los brokers alojados en producción.

Empieza con ConnectMQ

Crea un broker gratuito en app.connectmq.com con el plan Serverless, sin tarjeta de crédito, copia las credenciales generadas y usa el cliente MQTT en el navegador del portal para confirmar que el broker funciona antes de depurar el código de tu propio dispositivo.

Preguntas frecuentes

¿Por qué mosquitto_sub solo dice "A TLS error occurred"?

Los clientes de mosquitto ocultan el detalle de OpenSSL. Ejecuta openssl s_client con el mismo host, puerto y archivo de CA para ver el error de verificación exacto.

¿Puede un usuario o una contraseña incorrectos provocar un fallo del handshake TLS?

No. Las credenciales se comprueban después del handshake, dentro del intercambio CONNECT de MQTT. Un problema de credenciales aparece como un rechazo en el CONNACK, no como un error TLS.

Mi cliente funciona en la Wi-Fi de casa pero no en la oficina. ¿Por qué?

Probablemente la red de la oficina bloquea el puerto 8883 o inspecciona el tráfico TLS. Pruébalo con Test-NetConnection o nc e intenta usar WebSocket seguro en el puerto 8084.

¿Debo desactivar la verificación de certificados para conseguir conectarme?

No. Desactivar la verificación oculta el problema real y elimina la protección frente a brokers impostores. Corrige en su lugar la CA, el hostname o el reloj.

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

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.