Sí, curl habla MQTT. Desde la versión 7.70.0 curl entiende URLs mqtt://: un GET (lo predeterminado) se suscribe al topic de la ruta de la URL, y enviar datos con -d publica en él. Si curl ya está en tu servidor, runner de CI o contenedor, puedes comprobar que un broker acepta conexiones y enruta un mensaje sin instalar nada más.
# Suscribirse (bloquea hasta que pulses Ctrl+C)
curl mqtt://broker.connectmq.com/testmqtt/curl/demo
# Publicar (en una segunda terminal)
curl -d "hello from curl" mqtt://broker.connectmq.com/testmqtt/curl/demoEsta guía explica cómo se comportan esos comandos, el formato exacto de la salida, las limitaciones que importan y cuándo conviene pasar a un cliente MQTT de verdad.
Comprueba que tu curl tiene MQTT
MQTT es una funcionalidad que se decide al compilar. Busca mqtt en la lista de protocolos:
curl -V | grep -i protocols
# Protocols: dict file ftp ftps gopher ... mqtt pop3 pop3s rtsp ...Si falta mqtt, curl responde a una URL mqtt:// con curl: (1) Protocol "mqtt" not supported (las versiones antiguas añaden "or disabled in libcurl"). Las versiones actuales de macOS, la mayoría de las distribuciones Linux y la imagen Docker oficial curlimages/curl lo incluyen; una compilación mínima puede no hacerlo.
docker run --rm curlimages/curl -d "hi" mqtt://broker.connectmq.com/testmqtt/curl/demoCómo traduce curl MQTT a URLs
| Acción de curl | Comportamiento MQTT |
|---|---|
curl mqtt://host/a/b | CONNECT, SUBSCRIBE a a/b, imprime cada mensaje hasta que lo interrumpas |
curl -d data mqtt://host/a/b | CONNECT, PUBLISH de data en a/b (QoS 0), DISCONNECT |
mqtt://host:1884/... | Puerto no estándar (el predeterminado es 1883) |
-u user:pass | Usuario y contraseña en el paquete CONNECT |
--max-time 10 | Detiene una suscripción tras 10 segundos |
El topic es la ruta de la URL sin la barra inicial, así que mqtt://broker.connectmq.com/testmqtt/curl/demo significa el topic testmqtt/curl/demo. curl genera un client ID aleatorio que empieza por curl (por ejemplo curlMsFB2wMW, visible con -v) y usa MQTT 3.1.1.
Publicar mensajes
-d acepta una cadena, o @archivo para leer el payload de un archivo:
# Texto plano
curl -d "21.5" mqtt://broker.connectmq.com/testmqtt/curl/sensor/temp
# JSON desde la shell
curl -d '{"temp":21.5,"unit":"C"}' mqtt://broker.connectmq.com/testmqtt/curl/sensor/json
# Payload desde un archivo, con los bytes exactamente como están
curl --data-binary @reading.json mqtt://broker.connectmq.com/testmqtt/curl/sensor/json
# Con credenciales (usuario y contraseña de tu broker)
curl -u "$MQTT_USER:$MQTT_PASS" -d "on" mqtt://broker.connectmq.com/testmqtt/curl/relay/1Una publicación correcta no imprime nada y termina con estado 0, lo que hace a curl práctico en scripts y health checks. Ten en cuenta que el código 0 solo significa que el broker aceptó la conexión y que curl envió el PUBLISH: con QoS 0 no hay confirmación, así que no demuestra que alguien haya recibido el mensaje.
Suscribirse y leer la salida
Una suscripción dura hasta que pulsas Ctrl+C o hasta que un límite de tiempo la detiene:
curl -N --max-time 30 mqtt://broker.connectmq.com/testmqtt/curl/sensor/temp-N (--no-buffer) hace que curl escriba cada mensaje de inmediato en lugar de acumular la salida cuando la rediriges a otro programa. Al alcanzar el límite de tiempo, curl termina con el código 28 (operation timed out). Es la forma normal en que acaba una suscripción con límite, no un error.
Los comodines deben ir codificados en la URL
+ se puede escribir tal cual, pero # inicia un fragmento de URL y se recorta antes de que curl envíe nada. Codifícalo como %23:
curl -N 'mqtt://broker.connectmq.com/testmqtt/curl/%23' # testmqtt/curl/#
curl -N 'mqtt://broker.connectmq.com/testmqtt/curl/+/temp' # un solo nivel¿No sabes qué coincide con un filtro? Pruébalo en el topic matcher o lee topics y comodines en MQTT.
El formato de salida
curl no imprime los mensajes como líneas de texto. Por cada PUBLISH que recibe, escribe:
- dos bytes: la longitud del topic (big-endian),
- el nombre del topic,
- el payload, sin separador ni salto de línea final.
Esta es una captura real de un mensaje en una suscripción con comodín:
00000000: 0011 7465 7374 6d71 7474 2f63 7572 6c2f ..testmqtt/curl/
00000010: 612f 627b 2274 223a 3231 2e35 7d a/b{"t":21.5}0x0011 es 17, la longitud del topic testmqtt/curl/a/b, y el payload JSON viene justo después. Basta para saber "¿ha llegado algo?", pero es incómodo de analizar cuando llegan varios mensajes juntos. Un poco de Python puede separar el flujo:
import sys
# Separa el flujo de curl en pares topic/payload.
# Funciona si los payloads son pequeños y llegan uno por lectura; curl no indica la longitud del payload.
buf = sys.stdin.buffer
while True:
head = buf.read(2)
if len(head) < 2:
break
n = int.from_bytes(head, "big")
topic = buf.read(n).decode()
payload = buf.read1(65536) # lo que haya llegado con este mensaje
print(f"{topic} {payload.decode(errors='replace')}", flush=True)Como curl no escribe la longitud del payload, ningún parser puede ser totalmente fiable. Si necesitas procesar mensajes desde un programa, eso ya es motivo suficiente para usar mosquitto_sub -v o una librería.
Limitaciones que debes conocer
| Funcionalidad | curl | mosquitto_pub / mosquitto_sub |
|---|---|---|
| Versión de MQTT | 3.1.1 | 3.1, 3.1.1, 5 |
| QoS al publicar | Solo 0 | 0, 1, 2 |
| Publicación retenida | No | -r |
TLS (mqtts, puerto 8883) | No | --cafile, -p 8883 |
| WebSockets | No (ws:// en curl es un WebSocket simple, no MQTT) | No (usa un cliente de navegador) |
| Client ID propio, keep-alive, Last Will | No | Sí |
| Salida de suscripción legible | Formato binario | -v imprime líneas topic payload |
La carencia de TLS es la importante. El manual de curl lo dice claramente: "MQTT over TLS is not supported (yet)." Nunca envíes credenciales de producción con -u por el puerto 1883 sin cifrar en una red que no sea de confianza: viajan en texto plano. Para cualquier cosa más allá de un smoke test contra un listener TCP sin cifrar, usa mosquitto_pub y mosquitto_sub o MQTT CLI, que soportan TLS, MQTT 5 y todos los niveles de QoS.
Errores y códigos de salida
| Lo que ves | Significado | Solución |
|---|---|---|
curl: (1) Protocol "mqtt" not supported | Tu compilación no tiene MQTT | Usa otra compilación o la imagen curlimages/curl |
curl: (7) Failed to connect to ... port 1883 | No hay nada escuchando o un firewall lo bloquea | Revisa host, puerto y reglas de firewall |
curl: (8) Weird server reply | El broker rechazó el CONNECT | Ejecuta con -v y lee el CONNACK |
Expected 0000 but got 0005 (con -v) | Código de retorno CONNACK 5: no autorizado | Añade o corrige -u user:pass, revisa las ACL |
curl: (28) Operation timed out | Se alcanzó --max-time | Esperado en suscripciones con límite de tiempo |
Con -v, los dos últimos dígitos de Expected 0000 but got 00XX son el código de retorno CONNACK de MQTT 3.1.1: 01 versión de protocolo no aceptable, 02 identificador rechazado, 03 servidor no disponible, 04 usuario o contraseña incorrectos, 05 no autorizado. La búsqueda de reason codes MQTT explica cada uno, y códigos de razón CONNACK profundiza en las causas.
Un smoke test con curl para scripts y CI
Los puntos fuertes de curl son que está en todas partes y que devuelve códigos de salida limpios. Este script se suscribe en segundo plano, publica un token único y comprueba que vuelve:
#!/usr/bin/env bash
set -u
HOST="${MQTT_HOST:-broker.connectmq.com}"
TOPIC="testmqtt/curl-smoke/$(date +%s)-$$"
TOKEN="ping-$RANDOM"
OUT="$(mktemp)"
curl -sN --max-time 5 "mqtt://$HOST/$TOPIC" > "$OUT" &
SUB=$!
sleep 1 # da tiempo a que llegue el SUBSCRIBE
if ! curl -s -d "$TOKEN" "mqtt://$HOST/$TOPIC"; then
echo "FALLO: publicación rechazada (exit $?)"; exit 1
fi
wait "$SUB" # termina con 28 al alcanzar --max-time
if grep -q "$TOKEN" "$OUT"; then
echo "OK: mensaje enrutado a través de $HOST"
else
echo "FALLO: mensaje no recibido"; exit 1
fi
rm -f "$OUT"Ejecútalo desde la máquina o el pipeline que necesita acceso al broker: si pasa, demuestra a la vez DNS, la ruta TCP, el CONNECT y el enrutamiento. Para una versión más completa con TLS y pruebas de QoS, consulta el script de smoke test MQTT y MQTT en scripts Bash.
Para ver los mensajes que generan tus comandos curl, abre el cliente MQTT online y suscríbete al mismo topic. Verlos llegar al navegador confirma ambas direcciones de una vez.
Mira los mensajes de curl en el cliente online
curl es la forma más rápida de responder "¿puede esta máquina llegar al broker y publicar?" sin instalar nada. Su soporte MQTT es básico a propósito: QoS 0, sin retain, sin TLS y salida con formato binario. Úsalo para smoke tests y comprobaciones rápidas, y pasa a mosquitto_pub, MQTT CLI o una librería cliente cuando necesites seguridad, fiabilidad o una salida legible.
Preguntas frecuentes
¿curl soporta MQTT sobre TLS (mqtts://)?
No en las versiones de curl que traen la mayoría de los sistemas: el manual de curl indica que MQTT sobre TLS no está soportado. Usa curl solo contra el puerto TCP 1883 sin cifrar para comprobaciones rápidas, y usa mosquitto_pub, MQTT CLI o una librería para TLS en el puerto 8883.
¿Puede curl publicar mensajes retenidos o con QoS 1?
No. curl publica con QoS 0 y el flag retain desactivado, y no hay ninguna opción para cambiarlo. Si necesitas mensajes retenidos, QoS 1/2 o propiedades de MQTT 5, usa mosquitto_pub o el cliente online.
¿Por qué curl muestra caracteres raros antes de cada mensaje MQTT?
Al suscribirse, curl escribe cada PUBLISH recibido como 2 bytes con la longitud del topic, el nombre del topic y después el payload, sin separador. Los dos primeros bytes son binarios y aparecen como basura en la terminal. Pasa la salida por xxd o por un pequeño parser para leerla.


