Oui, curl parle MQTT. Depuis la version 7.70.0, curl comprend les URL mqtt:// : un GET (le comportement par défaut) s'abonne au topic indiqué dans le chemin de l'URL, et envoyer des données avec -d publie sur ce topic. Si curl est déjà présent sur votre serveur, votre runner CI ou votre conteneur, vous pouvez vérifier qu'un broker accepte les connexions et route un message sans rien installer d'autre.
# S'abonner (bloque jusqu'à Ctrl+C)
curl mqtt://broker.connectmq.com/testmqtt/curl/demo
# Publier (dans un second terminal)
curl -d "hello from curl" mqtt://broker.connectmq.com/testmqtt/curl/demoCe guide explique le comportement de ces commandes, le format exact de la sortie, les limites qui comptent et le moment où il vaut mieux passer à un vrai client MQTT.
Vérifier que votre curl prend en charge MQTT
MQTT est une fonctionnalité choisie à la compilation. Cherchez mqtt dans la liste des protocoles :
curl -V | grep -i protocols
# Protocols: dict file ftp ftps gopher ... mqtt pop3 pop3s rtsp ...Si mqtt est absent, curl répond à une URL mqtt:// par curl: (1) Protocol "mqtt" not supported (les anciens builds ajoutent "or disabled in libcurl"). Les versions actuelles de macOS, la plupart des distributions Linux et l'image Docker officielle curlimages/curl l'incluent ; un build minimal peut ne pas l'avoir.
docker run --rm curlimages/curl -d "hi" mqtt://broker.connectmq.com/testmqtt/curl/demoComment curl associe MQTT aux URL
| Action curl | Comportement MQTT |
|---|---|
curl mqtt://host/a/b | CONNECT, SUBSCRIBE à a/b, affiche chaque message jusqu'à interruption |
curl -d data mqtt://host/a/b | CONNECT, PUBLISH de data sur a/b (QoS 0), DISCONNECT |
mqtt://host:1884/... | Port non standard (le port par défaut est 1883) |
-u user:pass | Nom d'utilisateur et mot de passe dans le paquet CONNECT |
--max-time 10 | Arrête un abonnement au bout de 10 secondes |
Le topic est le chemin de l'URL sans la barre oblique initiale : mqtt://broker.connectmq.com/testmqtt/curl/demo désigne donc le topic testmqtt/curl/demo. curl génère un client ID aléatoire qui commence par curl (par exemple curlMsFB2wMW, visible avec -v) et utilise MQTT 3.1.1.
Publier des messages
-d accepte une chaîne, ou @fichier pour lire le payload dans un fichier :
# Texte brut
curl -d "21.5" mqtt://broker.connectmq.com/testmqtt/curl/sensor/temp
# JSON depuis le shell
curl -d '{"temp":21.5,"unit":"C"}' mqtt://broker.connectmq.com/testmqtt/curl/sensor/json
# Payload depuis un fichier, octets conservés à l'identique
curl --data-binary @reading.json mqtt://broker.connectmq.com/testmqtt/curl/sensor/json
# Avec identifiants (nom d'utilisateur et mot de passe de votre broker)
curl -u "$MQTT_USER:$MQTT_PASS" -d "on" mqtt://broker.connectmq.com/testmqtt/curl/relay/1Une publication réussie n'affiche rien et se termine avec le statut 0, ce qui rend curl pratique dans les scripts et les health checks. Gardez à l'esprit que le code 0 signifie seulement que le broker a accepté la connexion et que curl a envoyé le PUBLISH : en QoS 0, il n'y a pas d'accusé de réception, cela ne prouve donc pas que quelqu'un a reçu le message.
S'abonner et lire la sortie
Un abonnement dure jusqu'à Ctrl+C ou jusqu'à ce qu'une limite de temps l'arrête :
curl -N --max-time 30 mqtt://broker.connectmq.com/testmqtt/curl/sensor/temp-N (--no-buffer) force curl à écrire chaque message immédiatement au lieu de mettre la sortie en tampon quand vous la redirigez vers un autre programme. Quand la limite de temps est atteinte, curl se termine avec le code 28 (operation timed out). C'est la fin normale d'un abonnement limité dans le temps, pas une erreur.
Les wildcards doivent être encodés dans l'URL
+ peut s'écrire tel quel, mais # ouvre un fragment d'URL et est coupé avant même que curl n'envoie quoi que ce soit. Encodez-le en %23 :
curl -N 'mqtt://broker.connectmq.com/testmqtt/curl/%23' # testmqtt/curl/#
curl -N 'mqtt://broker.connectmq.com/testmqtt/curl/+/temp' # un seul niveauVous ne savez pas ce qu'un filtre couvre ? Testez-le dans le topic matcher ou lisez topics et wildcards MQTT.
Le format de sortie
curl n'affiche pas les messages sous forme de lignes de texte. Pour chaque PUBLISH reçu, il écrit :
- deux octets : la longueur du topic (big-endian),
- le nom du topic,
- le payload, sans séparateur ni saut de ligne final.
Voici une capture réelle d'un message sur un abonnement avec wildcard :
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 vaut 17, la longueur du topic testmqtt/curl/a/b, et le payload JSON suit directement. Cela suffit pour savoir « est-ce que quelque chose est arrivé ? », mais c'est pénible à analyser quand plusieurs messages arrivent ensemble. Un peu de Python permet de découper le flux :
import sys
# Découpe le flux de curl en paires topic/payload.
# Fonctionne si les payloads sont petits et arrivent un par lecture ; curl n'indique pas la longueur du 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) # ce qui est arrivé avec ce message
print(f"{topic} {payload.decode(errors='replace')}", flush=True)Comme curl n'écrit pas la longueur du payload, aucun parser ne peut être totalement fiable. Si vous devez traiter les messages dans un programme, c'est à lui seul une bonne raison d'utiliser mosquitto_sub -v ou une bibliothèque.
Les limites à connaître
| Fonctionnalité | curl | mosquitto_pub / mosquitto_sub |
|---|---|---|
| Version MQTT | 3.1.1 | 3.1, 3.1.1, 5 |
| QoS en publication | 0 uniquement | 0, 1, 2 |
| Publication retenue | Non | -r |
TLS (mqtts, port 8883) | Non | --cafile, -p 8883 |
| WebSockets | Non (ws:// dans curl est un WebSocket simple, pas MQTT) | Non (utilisez un client navigateur) |
| Client ID personnalisé, keep-alive, Last Will | Non | Oui |
| Sortie d'abonnement lisible | Format binaire | -v affiche des lignes topic payload |
L'absence de TLS est la limite la plus importante. Le manuel de curl le dit clairement : "MQTT over TLS is not supported (yet)." N'envoyez jamais d'identifiants de production avec -u sur le port 1883 non chiffré à travers un réseau non fiable : ils circulent en clair. Pour tout ce qui dépasse un smoke test sur un listener TCP non chiffré, utilisez mosquitto_pub et mosquitto_sub ou MQTT CLI, qui prennent en charge TLS, MQTT 5 et tous les niveaux de QoS.
Erreurs et codes de sortie
| Ce que vous voyez | Signification | Correctif |
|---|---|---|
curl: (1) Protocol "mqtt" not supported | Votre build n'a pas MQTT | Utilisez un autre build ou l'image curlimages/curl |
curl: (7) Failed to connect to ... port 1883 | Rien n'écoute, ou un firewall bloque | Vérifiez l'hôte, le port et les règles du firewall |
curl: (8) Weird server reply | Le broker a refusé le CONNECT | Relancez avec -v et lisez le CONNACK |
Expected 0000 but got 0005 (avec -v) | Code de retour CONNACK 5 : non autorisé | Ajoutez ou corrigez -u user:pass, vérifiez les ACL |
curl: (28) Operation timed out | --max-time a été atteint | Normal pour un abonnement limité dans le temps |
Avec -v, les deux derniers chiffres de Expected 0000 but got 00XX sont le code de retour CONNACK de MQTT 3.1.1 : 01 version de protocole refusée, 02 identifiant rejeté, 03 serveur indisponible, 04 nom d'utilisateur ou mot de passe incorrect, 05 non autorisé. L'outil de recherche de reason codes MQTT explique chaque code, et les reason codes CONNACK détaille les causes.
Un smoke test curl pour les scripts et la CI
Les atouts de curl : il est partout et renvoie des codes de sortie propres. Ce script s'abonne en arrière-plan, publie un jeton unique et vérifie qu'il revient :
#!/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 # laisse le temps au SUBSCRIBE d'arriver
if ! curl -s -d "$TOKEN" "mqtt://$HOST/$TOPIC"; then
echo "ÉCHEC : publication refusée (exit $?)"; exit 1
fi
wait "$SUB" # se termine avec 28 quand --max-time est atteint
if grep -q "$TOKEN" "$OUT"; then
echo "OK : message routé via $HOST"
else
echo "ÉCHEC : message non reçu"; exit 1
fi
rm -f "$OUT"Lancez-le depuis la machine ou le pipeline qui doit accéder au broker : un succès prouve d'un coup le DNS, le chemin TCP, le CONNECT et le routage. Pour une version plus complète avec TLS et vérifications de QoS, consultez le script de smoke test MQTT et MQTT dans les scripts Bash.
Pour voir les messages produits par vos commandes curl, ouvrez le client MQTT en ligne et abonnez-vous au même topic. Les voir arriver dans le navigateur confirme les deux sens en une fois.
Voir les messages de curl dans le client en ligne
curl est le moyen le plus rapide de répondre à « cette machine peut-elle joindre le broker et publier ? » sans rien installer. Sa prise en charge de MQTT est volontairement basique : QoS 0, pas de retain, pas de TLS et une sortie au format binaire. Utilisez-le pour les smoke tests et les vérifications rapides, et passez à mosquitto_pub, MQTT CLI ou une bibliothèque cliente dès que vous avez besoin de sécurité, de fiabilité ou d'une sortie lisible.
Questions fréquentes
curl prend-il en charge MQTT sur TLS (mqtts://) ?
Pas dans les versions de curl livrées avec la plupart des systèmes : le manuel de curl indique que MQTT sur TLS n'est pas pris en charge. Utilisez curl uniquement sur le port TCP 1883 non chiffré pour des vérifications rapides, et mosquitto_pub, MQTT CLI ou une bibliothèque pour TLS sur le port 8883.
curl peut-il publier des messages retenus ou en QoS 1 ?
Non. curl publie en QoS 0 avec le flag retain désactivé, et aucune option ne permet de changer cela. Si vous avez besoin de messages retenus, de QoS 1/2 ou de propriétés MQTT 5, utilisez mosquitto_pub ou le client en ligne.
Pourquoi curl affiche-t-il des caractères bizarres avant chaque message MQTT ?
En abonnement, curl écrit chaque PUBLISH reçu sous la forme d'une longueur de topic sur 2 octets, du nom du topic puis du payload, sans séparateur. Les deux premiers octets sont binaires et apparaissent comme des caractères parasites dans le terminal. Passez la sortie dans xxd ou un petit parser pour la lire.


