Tutoriais

MQTT com curl: publicar e assinar pela linha de comando

Use o curl como cliente MQTT: publique com -d, assine com URLs mqtt://, codifique curingas, leia a saída binária e saiba quando migrar para o mosquitto_pub.

Atualizado · 7 min de leitura

MQTT com curl: publicar e assinar pela linha de comando

Sim, o curl fala MQTT. Desde a versão 7.70.0 o curl entende URLs mqtt://: um GET (o padrão) assina o topic que está no caminho da URL, e enviar dados com -d publica nele. Se o curl já está no seu servidor, runner de CI ou container, você consegue verificar se um broker aceita conexões e roteia uma mensagem sem instalar mais nada.

Os dois comandos de que você precisa
# Assinar (bloqueia até você apertar Ctrl+C)
curl mqtt://broker.connectmq.com/testmqtt/curl/demo

# Publicar (em um segundo terminal)
curl -d "hello from curl" mqtt://broker.connectmq.com/testmqtt/curl/demo

Este guia mostra como esses comandos se comportam, o formato exato da saída, as limitações que importam e quando vale trocar por um cliente MQTT de verdade.

Verifique se o seu curl tem MQTT

MQTT é um recurso definido na compilação. Procure mqtt na lista de protocolos:

O MQTT foi compilado?
curl -V | grep -i protocols
# Protocols: dict file ftp ftps gopher ... mqtt pop3 pop3s rtsp ...

Se mqtt não aparecer, o curl responde a uma URL mqtt:// com curl: (1) Protocol "mqtt" not supported (builds antigos acrescentam "or disabled in libcurl"). As versões atuais do macOS, a maioria das distribuições Linux e a imagem Docker oficial curlimages/curl incluem o suporte; um build mínimo pode não incluir.

Sem curl local com MQTT? Use a imagem oficial
docker run --rm curlimages/curl -d "hi" mqtt://broker.connectmq.com/testmqtt/curl/demo

Como o curl mapeia MQTT em URLs

Ação do curlComportamento MQTT
curl mqtt://host/a/bCONNECT, SUBSCRIBE em a/b, imprime cada mensagem até ser interrompido
curl -d data mqtt://host/a/bCONNECT, PUBLISH de data em a/b (QoS 0), DISCONNECT
mqtt://host:1884/...Porta fora do padrão (o padrão é 1883)
-u user:passUsuário e senha no pacote CONNECT
--max-time 10Encerra uma assinatura após 10 segundos

O topic é o caminho da URL sem a barra inicial, então mqtt://broker.connectmq.com/testmqtt/curl/demo significa o topic testmqtt/curl/demo. O curl gera um client ID aleatório que começa com curl (por exemplo curlMsFB2wMW, visível com -v) e usa MQTT 3.1.1.

Publicar mensagens

-d recebe uma string, ou @arquivo para ler o payload de um arquivo:

Variações de publicação
# Texto simples
curl -d "21.5" mqtt://broker.connectmq.com/testmqtt/curl/sensor/temp

# JSON a partir do shell
curl -d '{"temp":21.5,"unit":"C"}' mqtt://broker.connectmq.com/testmqtt/curl/sensor/json

# Payload de um arquivo, com os bytes exatamente como estão
curl --data-binary @reading.json mqtt://broker.connectmq.com/testmqtt/curl/sensor/json

# Com credenciais (usuário e senha do seu broker)
curl -u "$MQTT_USER:$MQTT_PASS" -d "on" mqtt://broker.connectmq.com/testmqtt/curl/relay/1

Uma publicação bem-sucedida não imprime nada e sai com status 0, o que torna o curl prático em scripts e health checks. Lembre que o código 0 só significa que o broker aceitou a conexão e que o curl enviou o PUBLISH: com QoS 0 não há confirmação, então isso não prova que alguém recebeu a mensagem.

Assinar e ler a saída

Uma assinatura roda até você apertar Ctrl+C ou até um limite de tempo encerrá-la:

Assinar por 30 segundos
curl -N --max-time 30 mqtt://broker.connectmq.com/testmqtt/curl/sensor/temp

-N (--no-buffer) faz o curl escrever cada mensagem na hora, em vez de acumular a saída quando você a redireciona para outro programa. Quando o limite de tempo é atingido, o curl sai com código 28 (operation timed out). Esse é o jeito normal de uma assinatura com limite terminar, não um erro.

Curingas precisam ser codificados na URL

+ pode ser escrito como está, mas # inicia um fragmento de URL e é cortado antes de o curl enviar qualquer coisa. Codifique como %23:

Assinatura com curinga multinível
curl -N 'mqtt://broker.connectmq.com/testmqtt/curl/%23'          # testmqtt/curl/#
curl -N 'mqtt://broker.connectmq.com/testmqtt/curl/+/temp'       # um nível

Não sabe o que um filtro abrange? Teste no topic matcher ou leia topics e curingas no MQTT.

O formato da saída

O curl não imprime mensagens como linhas de texto. Para cada PUBLISH recebido, ele escreve:

  1. dois bytes: o tamanho do topic (big-endian),
  2. o nome do topic,
  3. o payload, sem separador nem quebra de linha no final.

Esta é uma captura real de uma mensagem em uma assinatura com curinga:

xxd da saída de assinatura do curl
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 é 17, o tamanho do topic testmqtt/curl/a/b, e o payload JSON vem logo em seguida. Isso basta para saber "chegou alguma coisa?", mas é chato de analisar quando várias mensagens chegam juntas. Um pouco de Python consegue separar o fluxo:

curl-mqtt-split.py — uso: curl -sN mqtt://... | python3 curl-mqtt-split.py
import sys

# Separa o fluxo do curl em pares topic/payload.
# Funciona quando os payloads são pequenos e chegam um por leitura; o curl não informa o tamanho do 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)   # o que chegou junto com esta mensagem
    print(f"{topic}  {payload.decode(errors='replace')}", flush=True)

Como o curl não escreve o tamanho do payload, nenhum parser é totalmente confiável. Se você precisa processar mensagens em um programa, só isso já é motivo para usar mosquitto_sub -v ou uma biblioteca.

Limitações que você precisa conhecer

Recursocurlmosquitto_pub / mosquitto_sub
Versão do MQTT3.1.13.1, 3.1.1, 5
QoS na publicaçãoSó 00, 1, 2
Publicação retidaNão-r
TLS (mqtts, porta 8883)Não--cafile, -p 8883
WebSocketsNão (ws:// no curl é um WebSocket comum, não MQTT)Não (use um cliente no navegador)
Client ID próprio, keep-alive, Last WillNãoSim
Saída de assinatura legívelFormato binário-v imprime linhas topic payload

A falta de TLS é a mais importante. O manual do curl diz isso diretamente: "MQTT over TLS is not supported (yet)." Nunca envie credenciais de produção com -u pela porta 1883 sem criptografia em uma rede não confiável: elas trafegam em texto puro. Para qualquer coisa além de um smoke test contra um listener TCP sem TLS, use mosquitto_pub e mosquitto_sub ou o MQTT CLI, que suportam TLS, MQTT 5 e todos os níveis de QoS.

Erros e códigos de saída

O que você vêSignificadoCorreção
curl: (1) Protocol "mqtt" not supportedSeu build não tem MQTTUse outro build ou a imagem curlimages/curl
curl: (7) Failed to connect to ... port 1883Nada está escutando, ou um firewall bloqueiaVerifique host, porta e regras de firewall
curl: (8) Weird server replyO broker recusou o CONNECTRode com -v e leia o CONNACK
Expected 0000 but got 0005 (com -v)Código de retorno CONNACK 5: não autorizadoAdicione ou corrija -u user:pass, verifique as ACLs
curl: (28) Operation timed outO --max-time foi atingidoEsperado em assinaturas com limite de tempo

Com -v, os dois últimos dígitos de Expected 0000 but got 00XX são o código de retorno CONNACK do MQTT 3.1.1: 01 versão de protocolo inaceitável, 02 identificador rejeitado, 03 servidor indisponível, 04 usuário ou senha inválidos, 05 não autorizado. A consulta de reason codes MQTT explica cada um, e reason codes do CONNACK detalha as causas.

Um smoke test com curl para scripts e CI

Os pontos fortes do curl são estar em todo lugar e retornar códigos de saída limpos. Este script assina em segundo plano, publica um token único e verifica se ele voltou:

curl-mqtt-smoke.sh
#!/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                                   # dá tempo para o SUBSCRIBE chegar

if ! curl -s -d "$TOKEN" "mqtt://$HOST/$TOPIC"; then
  echo "FALHA: publicação rejeitada (exit $?)"; exit 1
fi

wait "$SUB"                               # sai com 28 quando --max-time é atingido
if grep -q "$TOKEN" "$OUT"; then
  echo "OK: mensagem roteada via $HOST"
else
  echo "FALHA: mensagem não recebida"; exit 1
fi
rm -f "$OUT"

Rode-o na máquina ou no pipeline que precisa acessar o broker: se passar, ele comprova DNS, o caminho TCP, o CONNECT e o roteamento de uma vez. Para uma versão mais completa com TLS e verificações de QoS, veja o script de smoke test MQTT e MQTT em scripts Bash.

Para ver as mensagens que seus comandos curl geram, abra o cliente MQTT online e assine o mesmo topic. Vê-las chegando no navegador confirma as duas direções de uma vez.

Veja as mensagens do curl no cliente online

O curl é o jeito mais rápido de responder "esta máquina alcança o broker e consegue publicar?" sem instalar nada. O suporte a MQTT dele é básico de propósito: QoS 0, sem retain, sem TLS e saída em formato binário. Use-o para smoke tests e verificações rápidas, e migre para mosquitto_pub, MQTT CLI ou uma biblioteca cliente quando precisar de segurança, confiabilidade ou uma saída legível.

Perguntas frequentes

O curl suporta MQTT sobre TLS (mqtts://)?

Não nas versões do curl que vêm na maioria dos sistemas: o manual do curl diz que MQTT sobre TLS não é suportado. Use o curl só contra a porta TCP 1883 sem criptografia para verificações rápidas, e use mosquitto_pub, MQTT CLI ou uma biblioteca para TLS na porta 8883.

O curl consegue publicar mensagens retidas ou com QoS 1?

Não. O curl publica com QoS 0 e com a flag retain desligada, e não existe opção para mudar isso. Se você precisa de mensagens retidas, QoS 1/2 ou propriedades do MQTT 5, use o mosquitto_pub ou o cliente online.

Por que o curl mostra caracteres estranhos antes de cada mensagem MQTT?

Ao assinar, o curl escreve cada PUBLISH recebido como 2 bytes com o tamanho do topic, o nome do topic e depois o payload, sem separador. Os dois primeiros bytes são binários e aparecem como lixo no terminal. Passe a saída pelo xxd ou por um pequeno parser para lê-la.