Tutorials

MQTT mit curl: Publish und Subscribe auf der Kommandozeile

curl als MQTT-Client nutzen: mit -d publishen, über mqtt://-URLs abonnieren, Wildcards kodieren, die Binärausgabe lesen und wissen, wann mosquitto_pub besser ist.

Aktualisiert · 7 Min. Lesezeit

MQTT mit curl: Publish und Subscribe auf der Kommandozeile

Ja, curl spricht MQTT. Seit Version 7.70.0 versteht curl mqtt://-URLs: Ein GET (der Standard) abonniert das Topic im URL-Pfad, und Daten mit -d zu senden publisht dorthin. Wenn curl schon auf deinem Server, CI-Runner oder Container liegt, kannst du prüfen, ob ein Broker Verbindungen annimmt und eine Nachricht routet, ohne irgendetwas zu installieren.

Die zwei Befehle, die du brauchst
# Abonnieren (blockiert, bis du Strg+C drückst)
curl mqtt://broker.connectmq.com/testmqtt/curl/demo

# Publishen (in einem zweiten Terminal)
curl -d "hello from curl" mqtt://broker.connectmq.com/testmqtt/curl/demo

Dieser Guide zeigt, wie sich diese Befehle verhalten, wie die Ausgabe genau aussieht, welche Einschränkungen wichtig sind und wann du besser zu einem echten MQTT-Client wechselst.

Prüfen, ob dein curl MQTT kann

MQTT ist ein Feature, das beim Kompilieren festgelegt wird. Such in der Protokollliste nach mqtt:

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

Fehlt mqtt, antwortet curl auf eine mqtt://-URL mit curl: (1) Protocol "mqtt" not supported (ältere Builds ergänzen "or disabled in libcurl"). Aktuelle macOS-Versionen, die meisten Linux-Distributionen und das offizielle Docker-Image curlimages/curl bringen es mit; ein Minimal-Build eventuell nicht.

Kein lokales curl mit MQTT? Nimm das offizielle Image
docker run --rm curlimages/curl -d "hi" mqtt://broker.connectmq.com/testmqtt/curl/demo

Wie curl MQTT auf URLs abbildet

curl-AktionMQTT-Verhalten
curl mqtt://host/a/bCONNECT, SUBSCRIBE auf a/b, gibt jede Nachricht aus, bis du abbrichst
curl -d data mqtt://host/a/bCONNECT, PUBLISH von data auf a/b (QoS 0), DISCONNECT
mqtt://host:1884/...Abweichender Port (Standard ist 1883)
-u user:passBenutzername und Passwort im CONNECT-Paket
--max-time 10Beendet ein Abo nach 10 Sekunden

Das Topic ist der URL-Pfad ohne den führenden Schrägstrich, mqtt://broker.connectmq.com/testmqtt/curl/demo bedeutet also das Topic testmqtt/curl/demo. curl erzeugt eine zufällige Client-ID, die mit curl beginnt (zum Beispiel curlMsFB2wMW, sichtbar mit -v), und nutzt MQTT 3.1.1.

Nachrichten publishen

-d nimmt einen String oder @datei, um den Payload aus einer Datei zu lesen:

Publish-Varianten
# Klartext
curl -d "21.5" mqtt://broker.connectmq.com/testmqtt/curl/sensor/temp

# JSON aus der Shell
curl -d '{"temp":21.5,"unit":"C"}' mqtt://broker.connectmq.com/testmqtt/curl/sensor/json

# Payload aus einer Datei, Bytes bleiben exakt erhalten
curl --data-binary @reading.json mqtt://broker.connectmq.com/testmqtt/curl/sensor/json

# Mit Zugangsdaten (Benutzername und Passwort deines Brokers)
curl -u "$MQTT_USER:$MQTT_PASS" -d "on" mqtt://broker.connectmq.com/testmqtt/curl/relay/1

Ein erfolgreicher Publish gibt nichts aus und endet mit Status 0, was curl für Skripte und Health Checks praktisch macht. Beachte aber: Exit-Code 0 heißt nur, dass der Broker die Verbindung angenommen und curl das PUBLISH gesendet hat. Bei QoS 0 gibt es keine Bestätigung, es beweist also nicht, dass jemand die Nachricht empfangen hat.

Abonnieren und die Ausgabe lesen

Ein Abo läuft, bis du Strg+C drückst oder ein Zeitlimit es beendet:

30 Sekunden lang abonnieren
curl -N --max-time 30 mqtt://broker.connectmq.com/testmqtt/curl/sensor/temp

-N (--no-buffer) sorgt dafür, dass curl jede Nachricht sofort schreibt, statt die Ausgabe zu puffern, wenn du sie an ein anderes Programm weiterleitest. Ist das Zeitlimit erreicht, endet curl mit Code 28 (operation timed out). So endet ein Abo mit Zeitlimit ganz normal, das ist kein Fehler.

Wildcards müssen URL-kodiert werden

+ kannst du direkt schreiben, aber # leitet ein URL-Fragment ein und wird abgeschnitten, bevor curl überhaupt etwas sendet. Kodiere es als %23:

Abo mit Multi-Level-Wildcard
curl -N 'mqtt://broker.connectmq.com/testmqtt/curl/%23'          # testmqtt/curl/#
curl -N 'mqtt://broker.connectmq.com/testmqtt/curl/+/temp'       # eine Ebene

Unsicher, was ein Filter matcht? Probier es im Topic Matcher aus oder lies MQTT-Topics und Wildcards.

Das Ausgabeformat

curl gibt Nachrichten nicht als Textzeilen aus. Für jedes empfangene PUBLISH schreibt es:

  1. zwei Bytes: die Topic-Länge (Big-Endian),
  2. den Topic-Namen,
  3. den Payload, ohne Trennzeichen und ohne abschließenden Zeilenumbruch.

Hier ein echter Mitschnitt einer Nachricht auf einem Wildcard-Abo:

xxd der Subscribe-Ausgabe von 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 ist 17, die Länge des Topics testmqtt/curl/a/b, und der JSON-Payload folgt direkt danach. Für „Ist überhaupt etwas angekommen?“ reicht das, aber wenn mehrere Nachrichten gleichzeitig eintreffen, ist es mühsam zu parsen. Ein bisschen Python kann den Stream aufteilen:

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

# Teilt den curl-Stream in Topic/Payload-Paare auf.
# Funktioniert, wenn Payloads klein sind und einzeln pro Read ankommen; curl liefert keine Payload-Länge.
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)   # was mit dieser Nachricht angekommen ist
    print(f"{topic}  {payload.decode(errors='replace')}", flush=True)

Da curl keine Payload-Länge schreibt, kann kein Parser völlig zuverlässig sein. Wenn du Nachrichten programmatisch verarbeiten musst, ist das allein schon ein guter Grund für mosquitto_sub -v oder eine Bibliothek.

Einschränkungen, die du kennen solltest

Featurecurlmosquitto_pub / mosquitto_sub
MQTT-Version3.1.13.1, 3.1.1, 5
Publish-QoSNur 00, 1, 2
Retained PublishNein-r
TLS (mqtts, Port 8883)Nein--cafile, -p 8883
WebSocketsNein (ws:// ist in curl ein einfacher WebSocket, kein MQTT)Nein (nimm einen Browser-Client)
Eigene Client-ID, Keep-Alive, Last WillNeinJa
Lesbare Subscribe-AusgabeBinäres Framing-v gibt topic payload-Zeilen aus

Die TLS-Lücke ist die wichtigste. Das curl-Handbuch sagt es direkt: "MQTT over TLS is not supported (yet)." Schick niemals Produktions-Zugangsdaten mit -u über den unverschlüsselten Port 1883 durch ein nicht vertrauenswürdiges Netz: Sie gehen im Klartext über die Leitung. Für alles, was über einen Smoke-Test gegen einen unverschlüsselten TCP-Listener hinausgeht, nimm mosquitto_pub und mosquitto_sub oder MQTT CLI. Beide unterstützen TLS, MQTT 5 und alle QoS-Stufen.

Fehler und Exit-Codes

Was du siehstBedeutungLösung
curl: (1) Protocol "mqtt" not supportedDein Build hat kein MQTTAnderen Build oder das Image curlimages/curl nutzen
curl: (7) Failed to connect to ... port 1883Nichts lauscht dort, oder eine Firewall blocktHost, Port und Firewall-Regeln prüfen
curl: (8) Weird server replyDer Broker hat das CONNECT abgelehntMit -v ausführen und das CONNACK lesen
Expected 0000 but got 0005 (mit -v)CONNACK-Return-Code 5: nicht autorisiert-u user:pass ergänzen oder korrigieren, ACLs prüfen
curl: (28) Operation timed out--max-time wurde erreichtBei Abos mit Zeitlimit erwartet

Mit -v sind die letzten zwei Ziffern von Expected 0000 but got 00XX der CONNACK-Return-Code von MQTT 3.1.1: 01 Protokollversion nicht akzeptiert, 02 Identifier abgelehnt, 03 Server nicht verfügbar, 04 falscher Benutzername oder falsches Passwort, 05 nicht autorisiert. Das MQTT-Reason-Code-Nachschlagewerk erklärt jeden Code, und CONNACK-Reason-Codes geht ausführlich auf die Ursachen ein.

Ein curl-Smoke-Test für Skripte und CI

Die Stärken von curl: Es ist überall vorhanden und liefert saubere Exit-Codes. Dieses Skript abonniert im Hintergrund, publisht ein eindeutiges Token und prüft, ob es zurückkommt:

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                                   # dem SUBSCRIBE Zeit zum Ankommen geben

if ! curl -s -d "$TOKEN" "mqtt://$HOST/$TOPIC"; then
  echo "FEHLER: Publish abgelehnt (exit $?)"; exit 1
fi

wait "$SUB"                               # endet mit 28, wenn --max-time erreicht ist
if grep -q "$TOKEN" "$OUT"; then
  echo "OK: Nachricht über $HOST geroutet"
else
  echo "FEHLER: Nachricht nicht empfangen"; exit 1
fi
rm -f "$OUT"

Führ es auf der Maschine oder in der Pipeline aus, die Zugriff auf den Broker braucht: Ein Erfolg belegt DNS, den TCP-Pfad, das CONNECT und das Routing in einem Durchgang. Eine ausführlichere Version mit TLS- und QoS-Checks findest du im MQTT-Smoke-Test-Skript und in MQTT in Bash-Skripten.

Um die Nachrichten deiner curl-Befehle zu sehen, öffne den Online-MQTT-Client und abonniere dasselbe Topic. Wenn sie im Browser ankommen, sind beide Richtungen auf einmal bestätigt.

curl-Nachrichten im Online-Client ansehen

curl ist der schnellste Weg, die Frage „Erreicht diese Maschine den Broker und kann sie publishen?“ ohne Installation zu beantworten. Die MQTT-Unterstützung ist bewusst einfach gehalten: QoS 0, kein Retain, kein TLS und binär geframte Ausgabe. Nutz curl für Smoke-Tests und schnelle Checks, und wechsle zu mosquitto_pub, MQTT CLI oder einer Client-Bibliothek, sobald du Sicherheit, Zuverlässigkeit oder lesbare Ausgabe brauchst.

Häufig gestellte Fragen

Unterstützt curl MQTT über TLS (mqtts://)?

Nicht in den curl-Versionen, die auf den meisten Systemen installiert sind: Laut curl-Handbuch wird MQTT über TLS nicht unterstützt. Nutze curl nur gegen den unverschlüsselten TCP-Port 1883 für schnelle Checks und für TLS auf Port 8883 mosquitto_pub, MQTT CLI oder eine Bibliothek.

Kann curl Retained- oder QoS-1-Nachrichten publishen?

Nein. curl publisht mit QoS 0 und gelöschtem Retain-Flag, und es gibt keine Option, das zu ändern. Wenn du Retained Messages, QoS 1/2 oder MQTT-5-Properties brauchst, nimm mosquitto_pub oder den Online-Client.

Warum zeigt curl vor jeder MQTT-Nachricht seltsame Zeichen an?

Beim Abonnieren schreibt curl jedes empfangene PUBLISH als 2-Byte-Topic-Länge, Topic-Namen und danach den Payload, ohne Trennzeichen. Die ersten zwei Bytes sind binär und erscheinen im Terminal als Zeichensalat. Leite die Ausgabe durch xxd oder einen kleinen Parser, um sie zu lesen.