This Node-RED MQTT tutorial shows you how to connect Node-RED to a broker, receive and publish messages, turn raw JSON into a live dashboard and fire alerts when values cross a threshold. You will start on the free public broker at broker.connectmq.com with an importable flow, then switch the same flow to a private, TLS-encrypted broker by changing one configuration node. Everything here works on a laptop, a Raspberry Pi or a Docker container.
Why Node-RED and MQTT Fit Together
Node-RED is a flow-based programming tool: you wire nodes together in a browser editor, and each message travels along the wires as a JavaScript object called msg. MQTT is a publish/subscribe protocol where clients exchange messages through a broker using topics. The two share the same mental model. An mqtt in node is a subscriber that injects messages into a flow, and an mqtt out node is a publisher that sends whatever reaches it.
That makes Node-RED a natural glue layer around an MQTT system. Typical jobs include:
- Translating between devices that speak different payload formats.
- Building quick operator dashboards without a front-end project.
- Watching thresholds and sending notifications by email or chat.
- Writing telemetry into a time-series database.
- Bridging protocols such as Modbus, OPC UA or HTTP into MQTT topics.
If you need a refresher on brokers, topics and wildcards first, read what an MQTT broker does. The rest of this guide assumes you know that + matches one topic level and # matches everything below a level.
Install Node-RED
Pick whichever installation suits your machine. Any current Node-RED 4.x release includes the MQTT nodes; you do not need extra packages for this tutorial until the dashboard step.
# Option 1: npm (requires Node.js 18 or newer)
sudo npm install -g --unsafe-perm node-red
node-red
# Option 2: Docker
docker run -d --name nodered -p 1880:1880 -v node_red_data:/data nodered/node-red:4.0
# Option 3: Raspberry Pi OS / Debian, official install script
bash <(curl -sL https://github.com/node-red/linux-installers/releases/latest/download/update-nodejs-and-node-red)
Open http://localhost:1880 (or the Pi's address) in a browser. For service setup, upgrades and Windows specifics, see our step-by-step guide to installing Node-RED, and if you prefer containers, running Node-RED in Docker covers volumes and restart policies. The official Node-RED documentation is the reference for every core node.
Step 1: Configure the MQTT Broker Node
Every MQTT node in Node-RED points to a shared configuration node of type mqtt-broker. Configure it once and reuse it across all flows; Node-RED then keeps a single connection to the broker, which matters on brokers that limit connections per account.
Drag an mqtt in node onto the canvas, double-click it, and click the pencil next to Server to add a new broker:
| Field | Value for the public broker | Notes |
|---|---|---|
| Server | broker.connectmq.com |
Hostname only, no mqtt:// prefix |
| Port | 1883 |
Use 8883 with TLS later |
| Protocol | MQTT V5 | V3.1.1 also works |
| Client ID | nodered-7f3a (choose your own) |
Must be non-empty and unique |
| Keep Alive | 60 |
Seconds |
| Use clean start | Checked | Fine for most dashboards |
On the Messages tab, set a birth message (connectmq/demo/nr-7f3a/status, payload online, retained, QoS 1) and a will message on the same topic with offline. Other clients can now see whether your Node-RED instance is alive.
Why fill in the client ID yourself? If you leave it empty, Node-RED generates one, which is fine on a test broker. On a production broker that binds client IDs to credentials, you must enter the exact ID you were issued. Two Node-RED instances (say, your laptop and your Pi) with the same client ID will repeatedly disconnect each other.
Step 2: Import a Working Flow
The fastest way to learn is to import a complete flow and take it apart. In the editor, open the menu, choose Import, paste the JSON below and click Import. Change every nr-7f3a to your own random suffix first so you are not sharing topics with other readers of this Node-RED MQTT tutorial.
[
{ "id": "tab_mqtt", "type": "tab", "label": "MQTT tutorial" },
{
"id": "cmq_broker", "type": "mqtt-broker", "name": "ConnectMQ public",
"broker": "broker.connectmq.com", "port": "1883", "clientid": "nodered-7f3a",
"autoConnect": true, "usetls": false, "protocolVersion": "5", "keepalive": "60",
"cleansession": true, "autoUnsubscribe": true,
"birthTopic": "connectmq/demo/nr-7f3a/status", "birthQos": "1", "birthRetain": "true", "birthPayload": "online", "birthMsg": {},
"closeTopic": "connectmq/demo/nr-7f3a/status", "closeQos": "1", "closeRetain": "true", "closePayload": "offline", "closeMsg": {},
"willTopic": "connectmq/demo/nr-7f3a/status", "willQos": "1", "willRetain": "true", "willPayload": "offline", "willMsg": {},
"userProps": "", "sessionExpiry": ""
},
{
"id": "inj_tick", "type": "inject", "z": "tab_mqtt", "name": "every 5 s",
"props": [{ "p": "payload" }], "repeat": "5", "crontab": "", "once": true, "onceDelay": "1",
"topic": "", "payload": "", "payloadType": "date", "x": 120, "y": 80, "wires": [["fn_sim"]]
},
{
"id": "fn_sim", "type": "function", "z": "tab_mqtt", "name": "simulate sensor",
"func": "const base = 25 + 6 * Math.sin(Date.now() / 60000);\nmsg.payload = {\n temperature: Math.round((base + Math.random()) * 10) / 10,\n humidity: Math.round(40 + Math.random() * 10),\n ts: Date.now()\n};\nreturn msg;",
"outputs": 1, "x": 310, "y": 80, "wires": [["out_tel"]]
},
{
"id": "out_tel", "type": "mqtt out", "z": "tab_mqtt", "name": "publish telemetry",
"topic": "connectmq/demo/nr-7f3a/line1/sensor1/telemetry", "qos": "0", "retain": "false",
"broker": "cmq_broker", "x": 530, "y": 80, "wires": []
},
{
"id": "in_tel", "type": "mqtt in", "z": "tab_mqtt", "name": "all sensors",
"topic": "connectmq/demo/nr-7f3a/+/+/telemetry", "qos": "1", "datatype": "auto-detect",
"broker": "cmq_broker", "nl": false, "rap": true, "rh": 0, "inputs": 0,
"x": 130, "y": 180, "wires": [["fn_alert", "dbg_tel"]]
},
{
"id": "fn_alert", "type": "function", "z": "tab_mqtt", "name": "alert on crossing 30 C",
"func": "const t = Number(msg.payload.temperature);\nif (Number.isNaN(t)) return null;\nconst key = msg.topic;\nconst wasHigh = context.get(key) || false;\nconst isHigh = t > 30;\ncontext.set(key, isHigh);\nif (isHigh === wasHigh) return null;\nmsg.topic = msg.topic.replace(/telemetry$/, 'alert');\nmsg.payload = { level: isHigh ? 'high' : 'normal', temperature: t, ts: Date.now() };\nreturn msg;",
"outputs": 1, "x": 360, "y": 180, "wires": [["out_alert", "dbg_alert"]]
},
{
"id": "out_alert", "type": "mqtt out", "z": "tab_mqtt", "name": "publish alert",
"topic": "", "qos": "1", "retain": "true", "broker": "cmq_broker", "x": 590, "y": 160, "wires": []
},
{
"id": "dbg_tel", "type": "debug", "z": "tab_mqtt", "name": "telemetry", "active": true,
"tosidebar": true, "console": false, "complete": "payload", "x": 340, "y": 240, "wires": []
},
{
"id": "dbg_alert", "type": "debug", "z": "tab_mqtt", "name": "alerts", "active": true,
"tosidebar": true, "console": false, "complete": "true", "x": 580, "y": 220, "wires": []
}
]
Click Deploy. Within a few seconds, the small status indicator under each MQTT node turns green with the word "connected", and the debug sidebar starts showing telemetry objects. Verify from a terminal that the messages really go through the broker:
mosquitto_sub -h broker.connectmq.com -p 1883 -i sub-cli-7f3a -v -t "connectmq/demo/nr-7f3a/#"
You should see the retained status message first, then a telemetry message every five seconds and, whenever the simulated temperature crosses 30, an alert.
Step 3: Understand the mqtt in and mqtt out Nodes
The imported flow uses each MQTT node in a specific way, and these settings are the ones you will change most often.
The mqtt in node
- Topic accepts wildcards.
connectmq/demo/nr-7f3a/+/+/telemetryreceives telemetry from every sensor on every line under your prefix, andmsg.topictells you which one sent it. - Output set to auto-detect parses JSON payloads into objects, leaves plain text as a string and passes binary data as a Buffer. If a device sometimes sends malformed JSON, choose a String instead and parse it yourself with a JSON node so you can catch errors.
- QoS is the maximum QoS you want to receive. The broker delivers at the lower of the publisher's and subscriber's QoS.
- Dynamic subscription: set the node to accept input and send it
msg.action = "subscribe"withmsg.topic. This lets one flow follow topics that are only known at runtime; the dynamic MQTT subscriptions in Node-RED guide has a full example.
The mqtt out node
- If the node's Topic field is empty, it publishes to
msg.topic. The alert branch relies on this: the function node rewrites the topic from.../telemetryto.../alert. - If the Topic field is filled, it overrides
msg.topic. A frequent beginner bug is setting both and wondering why messages land on the wrong topic. - Objects in
msg.payloadare serialised to JSON automatically. Numbers and booleans become strings. - Retain on alert and state topics means any client that subscribes later sees the latest value immediately. Leave it off for high-frequency telemetry.
For deeper JSON handling, including converting strings to objects and back, see the Node-RED JSON node. For writing logic like the alert function, read the Node-RED function node, which covers context storage, multiple outputs and error handling.
Step 4: Build a Node-RED MQTT Dashboard
Raw debug output is not something you can hand to an operator. Dashboard 2.0 is the current, actively maintained dashboard for Node-RED. Install it from Manage palette, search for @flowfuse/node-red-dashboard, or from the command line:
cd ~/.node-red
npm install @flowfuse/node-red-dashboard
# restart Node-RED afterwards
Then extend your flow:
- Add a function node after the
all sensorsmqtt in node with the code below. It splits each telemetry object into chart-friendly messages. - Wire its first output to a ui-gauge (range 0 to 50, label "Temperature") and also to a ui-chart (type line, X axis time).
- Wire the second output to a second ui-gauge for humidity.
- When you add the first widget, Node-RED asks you to create a page and a group; accept the defaults.
- Deploy and open
http://localhost:1880/dashboard.
// Split telemetry into one message per metric for gauges and charts
const sensor = msg.topic.split("/").slice(-2, -1)[0]; // e.g. "sensor1"
const p = msg.payload;
if (typeof p !== "object" || p === null) return null;
return [
{ topic: `${sensor} temperature`, payload: Number(p.temperature) },
{ topic: `${sensor} humidity`, payload: Number(p.humidity) }
];
Set this function node to two outputs on its Setup tab. Using the sensor name in msg.topic means a single chart automatically draws one line per sensor when you add more devices.
To control a device from the dashboard, add a ui-switch whose on and off payloads are ON and OFF, and wire it to an mqtt out node with a topic such as connectmq/demo/nr-7f3a/line1/fan1/set, QoS 1. Best practice is to drive the switch's displayed state from the device's own state topic rather than from the click, so the dashboard never shows a state the hardware did not reach. Layouts, themes and multi-page dashboards are covered in our Node-RED Dashboard 2.0 guide, and keeping long history is the topic of Node-RED with InfluxDB.
Step 5: Alerts and Automation
The alert on crossing 30 C function in the imported flow is deliberately written as an edge detector. It stores the previous state per topic in node context and only emits a message when the state changes. Without that, a sensor sitting at 31 degrees would trigger an alert every five seconds and flood your inbox.
From the alert topic you can fan out to any notification channel:
- Email with the
node-red-node-emailnode; see sending email alerts from Node-RED. - Telegram for instant phone notifications with a bot; see Node-RED Telegram alerts.
- Another MQTT topic consumed by a siren, a light tower or another system.
Because the alert itself is published back to MQTT as a retained message, Node-RED is not the only consumer. Any dashboard, mobile app or script subscribed to +/alert sees the same event, which keeps your automation logic in one place and your notification channels decoupled.
A useful pattern for larger setups is a hierarchical topic structure such as site/area/line/cell/device/metric, inspired by ISA-95. It lets a single wildcard subscription in Node-RED cover a whole line or the whole site, and it is the basis of the Unified Namespace approach described in MQTT, Sparkplug B and the Unified Namespace.
Step 6: Move to a Private Broker with TLS
The public broker is shared and readable by anyone, so it is only for testing. For real flows, use your own broker with encryption and credentials. You can run one yourself, but a fully managed MQTT service removes patching, certificates and uptime from your to-do list. With ConnectMQ, you sign up, choose a plan, create a broker and copy the generated username, password and client ID from the portal.
Open the ConnectMQ public configuration node and change it:
| Setting | New value |
|---|---|
| Server | broker.connectmq.com |
| Port | 8883 |
| Use TLS | Checked; add a TLS configuration with Verify server certificate enabled and no certificate files |
| Client ID | YOUR_CLIENT_ID from the portal |
| Security tab: Username / Password | YOUR_USERNAME / YOUR_PASSWORD |
| Topics | Replace connectmq/demo/nr-7f3a with your own namespace, for example acme/plant1 |
The broker uses a publicly trusted Let's Encrypt certificate, so Node-RED validates it with its built-in CA store and you do not upload any CA file. Credentials are stored in Node-RED's encrypted credentials file, not in the flow JSON, which is why exported flows never contain passwords. Set a credentialSecret in settings.js so that file stays readable after you move Node-RED to another machine.
Details such as client certificates, custom CAs and common handshake errors are in Node-RED MQTT over TLS. Also lock down the editor itself: an exposed Node-RED editor without a login is effectively remote code execution on your server. Securing Node-RED explains adminAuth, HTTPS and network exposure.
Node-RED MQTT Tutorial Troubleshooting
When a node shows "disconnected" or "connecting" forever, work through this checklist:
- Hostname typo or protocol prefix. The Server field takes
broker.connectmq.com, notmqtt://broker.connectmq.com. - Wrong port for TLS. Port 1883 is plain TCP, 8883 requires the Use TLS checkbox. Mixing them causes silent timeouts.
- Duplicate client ID. Another Node-RED instance, a test script or MQTT Explorer using the same ID kicks the connection off every few seconds.
- Bad credentials or client ID mismatch. The node log shows "Connection refused: Not authorized". Re-copy all three values from the portal.
- Outbound ports blocked. Run
nc -vz broker.connectmq.com 8883from the Node-RED host. Docker containers may also need correct DNS settings. - Messages arrive but look wrong. Check the mqtt in Output setting; a Buffer in the debug sidebar means the node is set to binary.
- Rate or size limits. On the public broker, keep each client under 10 messages per second and 64 KB per message; inject nodes set to very short intervals hit this quickly.
For log-level diagnosis of each of these, our dedicated Node-RED MQTT not connecting guide walks through the exact error messages.
Where to Go Next with Node-RED
Once the basics feel familiar, these guides extend the same broker connection into real projects:
- Industrial data: read PLC registers with Node-RED and Modbus or decode birth and data messages with Node-RED and Sparkplug B.
- Clean topic design: restructure messy device topics with Node-RED MQTT topic mapping.
- Reusable logic: package repeated nodes into Node-RED subflows.
- Production metrics: build an OEE dashboard in Node-RED.
Run Your Node-RED Flows on ConnectMQ
Node-RED handles the logic; the broker carries every message between your flows, devices and dashboards. ConnectMQ's free Serverless plan needs no credit card and gives you a private broker with TLS on port 8883, MQTT 5, up to 25 connections and an in-browser MQTT client to inspect topics while you build. When your flows grow, paid Serverless adds shared subscriptions for load-balancing several Node-RED instances, and Dedicated plans give you an isolated cluster. Create your free broker at app.connectmq.com, paste the credentials into your broker node, and deploy. To weigh hosting options in more depth, read our guide to running an MQTT broker in the cloud, or start testing right away on the public MQTT test broker.
Frequently Asked Questions
Does Node-RED include an MQTT broker?
No. Node-RED ships MQTT client nodes only. You connect them to an external broker, such as a hosted broker or a self-run Mosquitto instance; community broker nodes exist but are not recommended for production.
How many connections does Node-RED use for MQTT?
One per mqtt-broker configuration node, no matter how many mqtt in and mqtt out nodes share it. Reuse a single configuration node to stay within connection limits.
Why does my mqtt in node output a string instead of an object?
The Output setting is probably set to a String, or the payload is not valid JSON. Switch to auto-detect or a parsed JSON object, or add a JSON node to parse it explicitly.
Can Node-RED use MQTT 5 features?
Yes. Set the protocol to MQTT V5 in the broker node to use features such as user properties, message expiry, response topics and session expiry from the mqtt in and mqtt out nodes.
Is it safe to use the public broker with Node-RED?
It is fine for learning and demos, but anyone can read public topics. Use unique topic prefixes, send no sensitive data and move to a private broker with TLS before connecting real equipment.



