Python MQTT TLS marca la diferencia entre enviar tu usuario, tu contraseña y los datos de tus sensores por internet en texto plano o enviarlos cifrados y autenticados. En este tutorial configurarás paho-mqtt para conectarse al puerto 8883 usando los certificados CA de confianza de tu sistema operativo, mantendrás activada la verificación del hostname y corregirás los errores de certificado que frenan a la mayoría. Todos los ejemplos se conectan a broker.connectmq.com, el servidor MQTT público gratuito de ConnectMQ para pruebas y prototipos, que sirve un certificado de Let's Encrypt de confianza pública, así que no necesitas archivos de CA personalizados.
Qué aporta TLS a MQTT
MQTT por sí mismo no tiene cifrado. En el puerto 1883 todo, incluido el paquete CONNECT con tus credenciales, viaja como bytes legibles. TLS envuelve la conexión TCP y te da tres garantías:
- Confidencialidad: nadie en la ruta de red puede leer payloads ni contraseñas.
- Integridad: nadie puede modificar los mensajes en tránsito sin que se detecte.
- Autenticación del servidor: tu cliente verifica que está hablando realmente con el broker que pidió y no con un impostor.
El tercer punto es el que la gente desactiva sin darse cuenta. El cifrado sin verificación te protege de los espías pasivos, pero no de un atacante activo que presenta su propio certificado. La guía de buenas prácticas de seguridad MQTT cubre el panorama general; este artículo se centra en configurar bien el lado del cliente.
ConnectMQ expone estos puertos en todos los brokers:
| Puerto | Transporte | Cifrado | Uso desde Python |
|---|---|---|---|
| 1883 | MQTT sobre TCP | No | Solo pruebas locales |
| 8883 | MQTT sobre TLS | Sí | Opción por defecto en producción |
| 8083 | MQTT sobre WebSocket | No | Rara vez necesario desde Python |
| 8084 | MQTT sobre WebSocket seguro, ruta /mqtt |
Sí | Cuando solo se permiten puertos web |
Requisitos previos
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install "paho-mqtt>=2.0"
Comprueba que el certificado del broker se valida desde tu máquina antes de escribir nada de Python. Este comando muestra la cadena y el resultado de la verificación:
openssl s_client -connect broker.connectmq.com:8883 -servername broker.connectmq.com -brief </dev/null
Busca Verification: OK. Si OpenSSL ya falla aquí, el problema está en el almacén de CA de tu sistema o en la red, no en tu código.
El cliente Python MQTT TLS mínimo
Guarda esto como tls_client.py y sustituye k7f3q2 por tu propia cadena aleatoria:
import ssl
import time
import uuid
import paho.mqtt.client as mqtt
BROKER = "broker.connectmq.com"
PORT = 8883
TOPIC = "connectmq/demo/k7f3q2/secure"
def on_connect(client, userdata, flags, reason_code, properties):
if reason_code.is_failure:
print(f"Connect failed: {reason_code}")
return
sock = client.socket()
print(f"Connected over {sock.version()} using {sock.cipher()[0]}")
client.subscribe(TOPIC, qos=1)
def on_message(client, userdata, msg):
print(f"{msg.topic}: {msg.payload.decode()}")
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=f"tls-{uuid.uuid4().hex[:8]}")
client.on_connect = on_connect
client.on_message = on_message
# Usa las CA de confianza del sistema operativo, exige un certificado válido
# y verifica que coincide con BROKER. Es el valor por defecto seguro.
client.tls_set(cert_reqs=ssl.CERT_REQUIRED, tls_version=ssl.PROTOCOL_TLS_CLIENT)
client.connect(BROKER, PORT, keepalive=60)
client.loop_start()
time.sleep(2)
client.publish(TOPIC, "hello over TLS", qos=1)
time.sleep(2)
client.disconnect()
client.loop_stop()
Ejecútalo y deberías ver algo como Connected over TLSv1.3 using TLS_AES_256_GCM_SHA384, seguido de tu mensaje de vuelta.
Qué hace tls_set()
Llamado sin el argumento ca_certs, tls_set() construye un contexto SSL que carga los certificados de confianza por defecto de la plataforma, exige que el servidor presente un certificado válido y comprueba que el nombre del certificado coincide con el host que pasaste a connect(). Los argumentos explícitos cert_reqs y tls_version de arriba son los valores por defecto, escritos para que la intención resulte evidente a quien lea el código después.
Si prefieres control total, construye tú mismo el contexto y pásaselo:
context = ssl.create_default_context()
context.minimum_version = ssl.TLSVersion.TLSv1_2
client.tls_set_context(context)
ssl.create_default_context() activa la comprobación del hostname y la verificación del certificado, y fijar una versión mínima rechaza los protocolos heredados. Usa tls_set() o tls_set_context(), pero no ambos.
Añadir credenciales para un broker privado
TLS protege las credenciales que envías, así que combina siempre ambas cosas. Con un broker creado en el portal de ConnectMQ:
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="YOUR_CLIENT_ID")
client.username_pw_set("YOUR_USERNAME", "YOUR_PASSWORD")
client.tls_set()
client.connect("broker.connectmq.com", 8883, keepalive=60)
Usa el nombre de host y el client ID que muestra el portal; el broker vincula los client IDs a las credenciales que emite. Carga los secretos desde variables de entorno (os.environ["MQTT_PASSWORD"]) en lugar de subirlos al control de versiones.
WebSocket seguro desde Python
Algunas redes bloquean todo salvo los puertos web estándar. paho puede tunelizar MQTT a través de WebSocket seguro en el puerto 8084 con la misma verificación TLS:
client = mqtt.Client(
mqtt.CallbackAPIVersion.VERSION2,
client_id=f"wss-{uuid.uuid4().hex[:8]}",
transport="websockets",
)
client.ws_set_options(path="/mqtt")
client.tls_set()
client.connect("broker.connectmq.com", 8084, keepalive=60)
Todo lo demás, callbacks, suscripciones y publicación, sigue igual. El artículo sobre MQTT sobre WebSocket explica cuándo tiene sentido este transporte.
Solución de los errores TLS habituales
La mayoría de los fallos TLS se reducen a un puñado de causas. Busca tu mensaje de error en esta tabla:
| Error | Causa probable | Solución |
|---|---|---|
CERTIFICATE_VERIFY_FAILED: unable to get local issuer certificate |
Python no encuentra las CA de confianza | Consulta las soluciones del almacén de CA más abajo |
Hostname mismatch, certificate is not valid for '...' |
Te conectaste por dirección IP o por un alias | Conéctate con el nombre de host exacto del broker |
certificate is not yet valid o certificate has expired |
El reloj del dispositivo está mal | Sincroniza la hora con NTP antes de conectar |
WRONG_VERSION_NUMBER |
Cliente TLS hablando con el puerto en claro 1883 | Usa el puerto 8883 |
| La conexión se queda colgada y expira | Cliente sin TLS hablando con el puerto TLS 8883, o puerto bloqueado | Llama a tls_set() o prueba el puerto 8084 |
Solucionar un almacén de CA ausente
- macOS con Python de python.org: ese Python viene sin acceso al llavero del sistema. Ejecuta el script "Install Certificates.command" de la carpeta de la aplicación Python, o apunta paho a certifi:
pip install certifiy luegoclient.tls_set(ca_certs=certifi.where()). - Imágenes Docker slim: instala el paquete de CA, por ejemplo
apt-get install -y ca-certificatesen imágenes basadas en Debian. - Redes corporativas con inspección TLS: un proxy puede sustituir los certificados por los suyos. Pide a TI la CA raíz corporativa y pásala con
ca_certs=en lugar de desactivar la verificación. - Linux embebido: actualiza el paquete de CA; las imágenes muy antiguas pueden no incluir el certificado ISRG Root X1 al que encadena Let's Encrypt.
Nunca "arregles" TLS desactivando la verificación
Encontrarás consejos para llamar a client.tls_insecure_set(True) o pasar cert_reqs=ssl.CERT_NONE. Ambos hacen desaparecer el error aceptando cualquier certificado de cualquiera, lo que elimina en silencio la autenticación del servidor. Úsalos solo para un diagnóstico rápido en tu propia red de laboratorio, nunca en código desplegado.
Evita también fijar (pinning) el certificado hoja del broker en tu cliente. Los certificados de Let's Encrypt se renuevan con regularidad, así que un certificado fijado dejará de funcionar en la siguiente renovación. Confía en la CA, que es lo que hace el almacén por defecto.
Lista de comprobación para producción
Antes de desplegar un cliente Python que use TLS, confirma cada punto:
- Se usa el puerto 8883 (o el 8084 con WebSocket) en todas partes; nada recurre al 1883.
- Se llama a
tls_set()otls_set_context()sintls_insecure_set(True). - El cliente se conecta por nombre de host, nunca por dirección IP.
- El dispositivo mantiene la hora exacta, sincronizada por NTP al arrancar.
- El paquete de CA del dispositivo se mantiene y se actualiza con el sistema operativo.
- Las credenciales provienen de variables de entorno o de un almacén de secretos.
- Hay lógica de reconexión; la guía de reconexión de paho-mqtt muestra un patrón robusto.
Siguientes pasos
Si eres nuevo en paho, el tutorial de paho mqtt en Python cubre los fundamentos de publicar y suscribirse, y la guía de aiomqtt y asyncio muestra cómo pasar un tls_context a un cliente asíncrono. Para clientes en otros lenguajes, el resumen de bibliotecas cliente MQTT compara el soporte de TLS entre ecosistemas. La documentación del módulo ssl de Python es la referencia de cada opción de contexto usada aquí.
Protege tu tráfico MQTT con ConnectMQ
ConnectMQ es un broker MQTT gestionado con TLS en el 8883 y WebSocket seguro en el 8084 activados de serie, respaldados por un certificado de Let's Encrypt gestionado automáticamente, para que tus clientes Python verifiquen el broker con el almacén de CA que ya tienen. Prueba en el servidor MQTT público gratuito y pasa después a un broker MQTT en la nube privado con credenciales generadas. Crea tu broker gratuito en el plan Serverless, sin tarjeta de crédito.
Preguntas frecuentes
¿Necesito descargar un certificado CA para conectarme con TLS?
No. El broker usa un certificado de Let's Encrypt que encadena a una raíz de confianza en los sistemas operativos actuales, así que tls_set() con el almacén de CA por defecto funciona.
¿Qué puerto usa MQTT sobre TLS?
El puerto estándar es el 8883. ConnectMQ también ofrece MQTT sobre WebSocket seguro en el puerto 8084 con la ruta /mqtt.
¿Por qué falla python mqtt tls cuando me conecto por dirección IP?
La verificación del hostname compara el nombre del certificado con el host al que te conectaste. Una dirección IP no coincide con el nombre del broker, así que la comprobación falla por diseño; usa el nombre de host.
¿Basta TLS para proteger mi cliente MQTT?
TLS protege la conexión, pero sigues necesitando credenciales robustas por dispositivo y un diseño cuidadoso de los topics. Combina TLS con credenciales únicas obtenidas del portal del broker.



