A Node-RED OPC UA flow lets you take values from modern PLCs, CNC controllers and SCADA servers and publish them to MQTT, where many consumers can use them without each one opening an OPC UA session. In this tutorial you will subscribe to OPC UA variables with node-red-contrib-opcua, map their node IDs to ISA-95 style MQTT topics, and publish each change with its quality and source timestamp. You can follow along with a free simulation server and a free MQTT broker.
Why Combine OPC UA and MQTT
OPC UA is excellent inside the plant: it has a rich information model, browsable address spaces, data types, security and subscriptions. It is less convenient for wide distribution. Every client holds a session with the server, firewalls between plant and cloud make direct connections awkward, and controllers can limit how many sessions they accept.
MQTT complements it. A single gateway holds one OPC UA session, receives changes through an OPC UA subscription, and publishes them to a broker. From there, dashboards, historians, MES and cloud analytics subscribe to topics they understand. The OPC UA vs MQTT comparison covers the trade-offs in detail. In short, OPC UA describes the machine, MQTT distributes the data, and Node-RED is a practical bridge between them for pilots and small to medium sites.
What You Need
| Component | Purpose | Example |
|---|---|---|
| Node-RED 4.x | The gateway | Laptop, industrial PC or Raspberry Pi |
node-red-contrib-opcua |
OPC UA client nodes | Install from the palette manager |
| OPC UA server | Data source | A PLC, or a free simulation server for testing |
| MQTT broker | Distribution | broker.connectmq.com for testing, a private broker for production |
Install the OPC UA nodes:
cd ~/.node-red
npm install node-red-contrib-opcua
# restart Node-RED afterwards
The package is large because it bundles the node-opcua stack, so the install can take a few minutes on a Raspberry Pi.
For a test server, the free Prosys OPC UA Simulation Server is widely used. Its default endpoint looks like opc.tcp://<your-pc>:53530/OPCUA/SimulationServer, and it exposes simulated variables such as Counter, Random and Sinusoid. Node IDs vary by version, so read the exact IDs from the server's Objects view; in many versions they look like ns=3;i=1001.
Step 1: Find the Node IDs
Every OPC UA variable has a node ID made of a namespace index and an identifier, for example ns=3;i=1001 (numeric) or ns=4;s=Line2.Filler.Temperature (string). You need these IDs before you can subscribe.
Two ways to find them:
- Use the OpcUa-Browser node in Node-RED: point it at the endpoint and browse from the Objects folder.
- Use a desktop client such as UaExpert, select a variable and copy its NodeId.
Note the data type too (Double, Int32, Boolean). The client node uses it when writing, and it tells you how to format the value in MQTT.
Step 2: Configure the OPC UA Client
Add an OpcUa-Client node and create an endpoint config:
- Endpoint:
opc.tcp://<server-host>:53530/OPCUA/SimulationServer - Security policy:
Noneand modeNonefor the first test - Action:
SUBSCRIBE - Interval:
1000ms (the publishing interval of the OPC UA subscription)
Then add one OpcUa-Item node per variable, with the item set to the node ID and the type set to the variable's data type. Wire a single inject node, set to inject once after 2 seconds, into all the item nodes, and wire the item nodes into the client. When the flow starts, each item asks the client to monitor that node, and from then on the client outputs a message whenever a value changes, with the value in msg.payload and the node ID in msg.topic.
Deploy and add a debug node after the client. You should see Counter incrementing and Random changing every second. If the client status shows a connection error, check the endpoint URL, especially the hostname: many servers advertise their own hostname, which must resolve from the Node-RED machine.
Step 3: Map OPC UA Node IDs to ISA-95 MQTT Topics
Publishing to a topic like opcua/ns=3;i=1001 would technically work, but nobody downstream knows what it means. Map each node ID to a place in your equipment hierarchy instead: enterprise or site, area, line, cell, and tag. That is the idea behind ISA-95 topic structures and the Unified Namespace. The guide to ISA-95 MQTT topics explains the levels.
A lookup table in a function node keeps the mapping explicit and reviewable:
// Function node "opc ua to isa-95 topic"
// Map OPC UA node IDs to equipment paths and tag names.
const PREFIX = "connectmq/demo/a7f3k2"; // demo prefix for the public broker
const MAP = {
"ns=3;i=1001": { path: "plant1/packaging/line2/filler", tag: "counter", unit: "" },
"ns=3;i=1002": { path: "plant1/packaging/line2/filler", tag: "pressure", unit: "bar" },
"ns=3;i=1004": { path: "plant1/packaging/line2/capper", tag: "torque", unit: "Nm" }
};
const nodeId = String(msg.topic);
const entry = MAP[nodeId];
if (!entry) {
node.warn("Unmapped node ID: " + nodeId);
return null;
}
// Quality: node-opcua status codes expose a name such as "Good" or "Bad..."
let quality = "Good";
if (msg.statusCode && typeof msg.statusCode === "object" && msg.statusCode.name) {
quality = msg.statusCode.name;
}
const value = typeof msg.payload === "number"
? Math.round(msg.payload * 1000) / 1000
: msg.payload;
msg.topic = `${PREFIX}/${entry.path}/${entry.tag}`;
msg.payload = {
value: value,
unit: entry.unit,
quality: quality,
sourceTimestamp: msg.sourceTimestamp || new Date().toISOString(),
nodeId: nodeId
};
msg.retain = true;
return msg;
The resulting topic, for example connectmq/demo/a7f3k2/plant1/packaging/line2/filler/pressure, has eight levels. That fits the public broker's limit of eight topic levels; on a private broker you would drop the demo prefix and could add an enterprise level.
Publishing with retain means any new subscriber immediately gets the latest value of each tag, which is exactly how a Unified Namespace should behave: the topic tree represents current state. Keep quality and source timestamp in the payload. A historian needs to know when the value changed at the source, not when Node-RED forwarded it, and a "Bad" quality value should not be trended as if it were real.
Step 4: Publish to MQTT
Add an mqtt out node with the topic and retain fields left empty, so the message properties are used. For testing, connect to broker.connectmq.com on port 1883 with a unique client ID; that is the free public MQTT broker, so replace a7f3k2 with your own random string and do not publish real production data there.
Check the output from a terminal:
mosquitto_sub -h broker.connectmq.com -p 1883 -i sub-opcua-a7f3k2 -v \
-t 'connectmq/demo/a7f3k2/plant1/#'
Each line shows a topic and a JSON payload with value, unit, quality and timestamp.
Writing Back to OPC UA (Carefully)
The same client node supports a WRITE action. An MQTT command flow would subscribe to a dedicated command topic, validate the value and range in a function node, set msg.topic to a fixed node ID and send it through an OpcUa-Item configured with the right data type. As with any OT write path, never let the incoming message choose the node ID, log every write, and publish an acknowledgement topic. Many sites keep the gateway read-only until there is a clear need.
Security for Production
The test setup uses no security. For a real controller:
- Use a secure policy. Choose
Basic256Sha256with modeSignAndEncryptin the endpoint config. - Trust certificates. On first connection, the server usually rejects the Node-RED client certificate. Move it to the trusted list on the server (in Prosys, on the Certificates tab), then reconnect.
- Use a dedicated user. Configure a read-only OPC UA user for the gateway if the server supports user authentication.
- Protect the MQTT side. Connect to a private broker over TLS on port 8883 with credentials, not the public test broker.
Troubleshooting Node-RED OPC UA Flows
| Symptom | Likely cause | Fix |
|---|---|---|
BadCertificateUntrusted or connection refused |
Client certificate not trusted | Trust it on the server, then redeploy |
| Connects, then fails with a hostname error | Server advertises an unresolvable hostname | Add a hosts entry or configure the server's advertised address |
BadNodeIdUnknown |
Wrong namespace index or identifier | Browse again; namespace indexes can differ between servers |
| No messages after deploy | Items were never sent to the client | Make sure the inject fires once on start |
| Values arrive only every few seconds | Large publishing interval or sampling interval | Lower the interval, within what the server allows |
| Gateway memory grows over time | Hundreds of items subscribed individually | Group items and keep the number of subscriptions small |
Scaling the Pattern
For a handful of machines, one Node-RED instance per site with a mapping table works well. As the table grows, move it into a JSON file or flow context loaded at startup, so engineers can review changes without touching code. When many vendors and protocols are involved, combine this flow with the Node-RED Modbus gateway for older devices, and consider Node-RED Sparkplug B if your consumers expect Sparkplug's birth, death and state handling. Commercial gateways are worth evaluating once you need hundreds of tags per site with central configuration.
All of these gateways need a broker they can trust. A cloud MQTT broker lets gateways at several sites publish to one namespace through outbound TLS connections. ConnectMQ detects ISA-95 and Unified Namespace topic structures and shows devices by enterprise, site, area, line and cell in its portal, which makes it easy to check that your mapping table produced the hierarchy you intended. For a refresher on what an MQTT broker does in this architecture, or on configuring the MQTT nodes, see the Node-RED MQTT tutorial.
Publish Your OPC UA Data to ConnectMQ
Sign up at app.connectmq.com with the free Serverless plan, no credit card required. Create a broker, paste the generated username, password and client ID into your Node-RED MQTT config node, switch to port 8883 with TLS, and drop the demo prefix from the mapping table. Your OPC UA tags then appear in the ConnectMQ portal organised by ISA-95 levels.
Frequently Asked Questions
Which Node-RED package is used for OPC UA?
The most widely used package is node-red-contrib-opcua, built on the node-opcua stack. It provides client, item, browser and server nodes for reading, writing, subscribing and testing.
Should I poll or subscribe to OPC UA values in Node-RED?
Subscribe whenever the server supports it. The server then sends only changes at the interval you set, which is lighter on the controller and gives faster updates than polling.
Why map OPC UA node IDs to ISA-95 MQTT topics?
Node IDs are meaningful only inside one server. ISA-95 style topics describe where data comes from in the plant, so every consumer can find and understand it without knowing the OPC UA address space.
Can Node-RED connect to OPC UA servers with security enabled?
Yes. Choose a security policy such as Basic256Sha256 with SignAndEncrypt and trust the Node-RED client certificate on the server. User and password authentication is also supported.



