Yes, curl speaks MQTT. Since version 7.70.0 curl understands mqtt:// URLs: a GET (the default) subscribes to the topic in the URL path, and sending data with -d publishes to it. If curl is already on your server, CI runner or container, you can check that a broker accepts connections and routes a message without installing anything else.
# Subscribe (blocks until you press Ctrl+C)
curl mqtt://broker.connectmq.com/testmqtt/curl/demo
# Publish (in a second terminal)
curl -d "hello from curl" mqtt://broker.connectmq.com/testmqtt/curl/demoThis guide covers how those commands behave, the exact output format, the limitations that matter, and when you should switch to a real MQTT client.
Check that your curl has MQTT
MQTT is a compile-time feature. Look for mqtt in the protocol list:
curl -V | grep -i protocols
# Protocols: dict file ftp ftps gopher ... mqtt pop3 pop3s rtsp ...If mqtt is missing, curl answers an mqtt:// URL with curl: (1) Protocol "mqtt" not supported (older builds add "or disabled in libcurl"). Current releases of macOS, most Linux distributions and the official curlimages/curl Docker image include it; a minimal build may not.
docker run --rm curlimages/curl -d "hi" mqtt://broker.connectmq.com/testmqtt/curl/demoHow curl maps MQTT onto URLs
| curl action | MQTT behaviour |
|---|---|
curl mqtt://host/a/b | CONNECT, SUBSCRIBE to a/b, print every message until interrupted |
curl -d data mqtt://host/a/b | CONNECT, PUBLISH data to a/b (QoS 0), DISCONNECT |
mqtt://host:1884/... | Non-default port (the default is 1883) |
-u user:pass | Username and password in the CONNECT packet |
--max-time 10 | Stop a subscription after 10 seconds |
The topic is the URL path without the leading slash, so mqtt://broker.connectmq.com/testmqtt/curl/demo means topic testmqtt/curl/demo. curl generates a random client ID that starts with curl (for example curlMsFB2wMW, visible with -v) and uses MQTT 3.1.1.
Publish messages
-d takes a string, or @file to read the payload from a file:
# Plain text
curl -d "21.5" mqtt://broker.connectmq.com/testmqtt/curl/sensor/temp
# JSON from the shell
curl -d '{"temp":21.5,"unit":"C"}' mqtt://broker.connectmq.com/testmqtt/curl/sensor/json
# Payload from a file, bytes kept exactly as they are
curl --data-binary @reading.json mqtt://broker.connectmq.com/testmqtt/curl/sensor/json
# With credentials (your broker's username and password)
curl -u "$MQTT_USER:$MQTT_PASS" -d "on" mqtt://broker.connectmq.com/testmqtt/curl/relay/1A successful publish prints nothing and exits with status 0, which makes curl handy in scripts and health checks. Keep in mind that exit code 0 only means the broker accepted the connection and curl sent the PUBLISH: at QoS 0 there is no acknowledgement, so it does not prove that anyone received the message.
Subscribe and read the output
A subscription runs until you press Ctrl+C or until a time limit stops it:
curl -N --max-time 30 mqtt://broker.connectmq.com/testmqtt/curl/sensor/temp-N (--no-buffer) makes curl write each message immediately instead of buffering output when you pipe it into another program. When the time limit is reached, curl exits with code 28 (operation timed out). That is the normal way a time-limited subscription ends, not an error.
Wildcards must be URL-encoded
+ can be written as is, but # starts a URL fragment and is cut off before curl sends anything. Encode it as %23:
curl -N 'mqtt://broker.connectmq.com/testmqtt/curl/%23' # testmqtt/curl/#
curl -N 'mqtt://broker.connectmq.com/testmqtt/curl/+/temp' # single levelNot sure what a filter matches? Try it in the topic matcher or read MQTT topics and wildcards.
The output format
curl does not print messages as text lines. For every PUBLISH it receives, it writes:
- two bytes: the topic length (big-endian),
- the topic name,
- the payload, with no separator or trailing newline.
Here is a real capture of one message on a wildcard subscription:
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 is 17, the length of the topic testmqtt/curl/a/b, and the JSON payload follows directly. That is fine for "did anything arrive?", but it is awkward to parse when several messages arrive together. A little Python can split the stream:
import sys
# Splits curl's stream into topic/payload pairs.
# Works when payloads are small and arrive one per read; curl does not frame payload length.
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) # whatever arrived with this message
print(f"{topic} {payload.decode(errors='replace')}", flush=True)Because curl writes no payload length, no parser can be fully reliable. If you need to process messages programmatically, that alone is a good reason to use mosquitto_sub -v or a library.
Limitations you should know
| Feature | curl | mosquitto_pub / mosquitto_sub |
|---|---|---|
| MQTT version | 3.1.1 | 3.1, 3.1.1, 5 |
| Publish QoS | 0 only | 0, 1, 2 |
| Retained publish | No | -r |
TLS (mqtts, port 8883) | No | --cafile, -p 8883 |
| WebSockets | No (ws:// in curl is a plain WebSocket, not MQTT) | No (use a browser client) |
| Custom client ID, keep-alive, Last Will | No | Yes |
| Readable subscribe output | Binary framing | -v prints topic payload lines |
The TLS gap is the important one. The curl manual says so directly: "MQTT over TLS is not supported (yet)." Never send production credentials with -u over plain port 1883 on an untrusted network: they travel in clear text. For anything beyond a smoke test against a plain-TCP listener, use mosquitto_pub and mosquitto_sub or MQTT CLI, both of which support TLS, MQTT 5 and every QoS level.
Errors and exit codes
| What you see | Meaning | Fix |
|---|---|---|
curl: (1) Protocol "mqtt" not supported | Your build has no MQTT | Use another build or the curlimages/curl image |
curl: (7) Failed to connect to ... port 1883 | Nothing is listening, or a firewall drops it | Check the host, port and firewall rules |
curl: (8) Weird server reply | The broker refused the CONNECT | Run with -v and read the CONNACK |
Expected 0000 but got 0005 (with -v) | CONNACK return code 5: not authorized | Add or correct -u user:pass, check ACLs |
curl: (28) Operation timed out | --max-time was reached | Expected for time-limited subscriptions |
With -v, the last two digits of Expected 0000 but got 00XX are the MQTT 3.1.1 CONNACK return code: 01 unacceptable protocol version, 02 identifier rejected, 03 server unavailable, 04 bad username or password, 05 not authorized. The MQTT reason code lookup explains each one, and CONNACK reason codes covers the causes in depth.
A curl smoke test for scripts and CI
curl's strengths are that it is everywhere and that it returns clean exit codes. This script subscribes in the background, publishes a unique token and checks that it came back:
#!/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 # give the SUBSCRIBE time to land
if ! curl -s -d "$TOKEN" "mqtt://$HOST/$TOPIC"; then
echo "FAIL: publish rejected (exit $?)"; exit 1
fi
wait "$SUB" # exits 28 when --max-time is reached
if grep -q "$TOKEN" "$OUT"; then
echo "OK: message routed through $HOST"
else
echo "FAIL: message not received"; exit 1
fi
rm -f "$OUT"Run it from the machine or pipeline that needs broker access: a pass proves DNS, the TCP path, the CONNECT and routing in one go. For a fuller version with TLS and QoS checks, see the MQTT smoke test script and MQTT in Bash scripts.
To watch the messages your curl commands produce, open the online MQTT client and subscribe to the same topic. Seeing them arrive in the browser confirms both directions at once.
Watch curl's messages in the online client
curl is the fastest way to answer "can this machine reach the broker and publish?" without installing anything. Its MQTT support is basic on purpose: QoS 0, no retain, no TLS and binary-framed output. Use it for smoke tests and quick checks, and switch to mosquitto_pub, MQTT CLI or a client library once you need security, reliability or readable output.
Frequently asked questions
Does curl support MQTT over TLS (mqtts://)?
Not in the curl versions shipped by most systems: the curl manual states that MQTT over TLS is not supported. Use curl only against plain TCP port 1883 for quick checks, and use mosquitto_pub, MQTT CLI or a library for TLS on port 8883.
Can curl publish retained or QoS 1 messages?
No. curl publishes with QoS 0 and the retain flag cleared, and there is no option to change either. If you need retained messages, QoS 1/2 or MQTT 5 properties, use mosquitto_pub or the online client.
Why does curl print strange characters before each MQTT message?
When subscribing, curl writes every PUBLISH it receives as a 2-byte topic length, the topic name and then the payload, with no separator. The first two bytes are binary, so they show up as garbage in a terminal. Pipe the output through xxd or a small parser to read it.


