How to Monitor Factory Machines in Real Time with MQTT and a Self-Hosted Dashboard
Most factory floors run legacy equipment that speaks Modbus. Most production managers want a live dashboard on their browser showing temperatures, pressures, cycle counts, and alarms. Between those two realities sits a protocol gap that a Node-RED gateway bridges in less than a day. This guide covers the complete stack: polling Modbus TCP registers from a PLC or sensor, bridging the data to a self-hosted MQTT broker, storing it in InfluxDB, visualising it in Grafana, and exposing the dashboard to remote users through a Localtonet tunnel. No cloud subscription required. No modifications to existing equipment.
📋 What's in this guide
Stack Architecture
The monitoring stack follows a layered architecture that is well-established in industrial IIoT deployments. Each layer has a single responsibility and the components are loosely coupled any layer can be replaced or upgraded without rebuilding the others.
Data flow from machine to dashboard
PLC / Sensor (Modbus TCP, port 502)
↓ Polled every 2 seconds by Node-RED modbus-read node
Node-RED (protocol gateway, runs on edge machine)
↓ Publishes structured JSON to topic factory/line1/temperature
Mosquitto (MQTT broker, port 1883)
↓ Telegraf subscribes and writes to InfluxDB
InfluxDB (time-series database, port 8086)
↓ Grafana queries via InfluxDB data source
Grafana (dashboard server, port 3000)
↓ Localtonet HTTP tunnel
Remote browser (manager, engineer, maintenance team)
| Component | Role | Default port | Installed on |
|---|---|---|---|
| Node-RED | Modbus TCP poller and MQTT publisher | 1880 | Edge gateway machine |
| Mosquitto | MQTT broker | 1883 | Edge gateway machine |
| Telegraf | MQTT subscriber and InfluxDB writer | N/A (agent) | Edge gateway machine |
| InfluxDB | Time-series database | 8086 | Edge gateway or server |
| Grafana | Visualisation and alerting | 3000 | Edge gateway or server |
| Localtonet | Remote HTTPS access tunnel | N/A (client) | Edge gateway machine |
Gateway Machine Setup
All software components run on a single gateway machine located inside the factory network. A Raspberry Pi 4 (4 GB RAM), an industrial edge PC, or any Linux machine on the same network segment as the PLCs and sensors is sufficient for a production line with up to several dozen data points. For larger installations with hundreds of devices, a dedicated x86 industrial PC is recommended.
sudo apt update && sudo apt upgrade -y
sudo apt install -y curl wget git
Step 1: Install the Mosquitto MQTT Broker
sudo apt install -y mosquitto mosquitto-clients
sudo systemctl enable mosquitto
sudo systemctl start mosquitto
Create a configuration file with authentication:
sudo nano /etc/mosquitto/conf.d/factory.conf
listener 1883 127.0.0.1
allow_anonymous false
password_file /etc/mosquitto/passwd
Create a user for Node-RED and Telegraf to connect with:
sudo mosquitto_passwd -c /etc/mosquitto/passwd factoryuser
sudo systemctl restart mosquitto
Test that the broker is accepting connections:
mosquitto_sub -h 127.0.0.1 -u factoryuser -P yourpassword -t factory/# &
Step 2: Install InfluxDB
curl https://repos.influxdata.com/influxdata-archive.key | \
gpg --dearmor | sudo tee /usr/share/keyrings/influxdata-keyring.gpg > /dev/null
echo "deb [signed-by=/usr/share/keyrings/influxdata-keyring.gpg] \
https://repos.influxdata.com/debian stable main" | \
sudo tee /etc/apt/sources.list.d/influxdata.list
sudo apt update
sudo apt install -y influxdb2
sudo systemctl enable influxdb
sudo systemctl start influxdb
Complete the initial setup by visiting http://localhost:8086 in a browser on the gateway machine,
or run the setup command directly:
influx setup \
--username admin \
--password yourpassword \
--org factory \
--bucket machines \
--retention 30d \
--force
Note the API token generated during setup Telegraf and Grafana both need it to authenticate with InfluxDB.
Step 3: Node-RED Modbus to MQTT Bridge
Node-RED polls Modbus TCP registers from PLCs and sensors, transforms the raw register values into structured JSON payloads, and publishes them to the MQTT broker. This is the protocol translation layer that converts the pull-based Modbus world into the event-driven MQTT world.
# Install Node-RED
sudo npm install -g --unsafe-perm node-red
# Install the Modbus TCP node palette
cd ~/.node-red
npm install node-red-contrib-modbus
# Start Node-RED
node-red
Open the Node-RED editor at http://localhost:1880 and install the Modbus nodes
from the Manage Palette menu if you prefer the UI approach.
Building the Modbus to MQTT flow
The flow reads holding registers from a Modbus TCP device and publishes the values to MQTT topics.
The example below reads three registers from a PLC at 192.168.10.10:
temperature (register 40001), pressure (register 40002), and motor speed (register 40003).
In the Node-RED editor, build a flow with the following nodes connected in sequence:
Add a modbus-read node
Configure the Modbus server to the PLC's IP address (192.168.10.10) and port 502.
Set data type to HoldingRegister, start address to 0,
quantity to 3, and poll rate to 2000 ms.
Unit ID should match the PLC's Modbus slave address (typically 1).
Add a function node to transform the payload
// Node-RED function node: transform Modbus registers to structured JSON
var registers = msg.payload;
// Apply scaling if raw values need conversion
// Example: register value 2240 represents 22.40 degrees (divide by 100)
msg.payload = {
temperature: registers[0] / 100,
pressure: registers[1] / 10,
motor_speed: registers[2],
timestamp: new Date().toISOString(),
line: "line1",
machine: "press_01"
};
msg.topic = "factory/line1/press_01";
return msg;
Add an mqtt out node
Configure the MQTT broker to 127.0.0.1:1883 with the credentials
you created for Mosquitto. Set the topic to factory/line1/press_01
and QoS to 1 (at least once delivery). Enable retain if you want subscribers
to receive the last known value immediately on connect.
Deploy the flow. Open a terminal and verify the data is arriving on the broker:
mosquitto_sub -h 127.0.0.1 -u factoryuser -P yourpassword -t factory/#
You should see JSON payloads arriving every two seconds:
{"temperature":22.4,"pressure":4.7,"motor_speed":1450,"timestamp":"2025-03-10T08:42:01.123Z","line":"line1","machine":"press_01"}
Modbus registers hold 16-bit unsigned integers (0 to 65535).
Physical values like temperatures and pressures are stored as scaled integers.
The scaling factor is defined in the PLC's documentation or in the device datasheet.
A common convention is to store 22.4°C as register value 2240,
requiring division by 100 in the function node. Always verify the scaling with the PLC programmer
or device documentation before treating register values as engineering units.
Step 4: Telegraf — MQTT to InfluxDB
Telegraf subscribes to the MQTT broker and writes incoming messages to InfluxDB. It is the persistence layer between the real-time event stream and the historical database.
sudo apt install -y telegraf
Create the Telegraf configuration for this factory setup:
sudo nano /etc/telegraf/telegraf.conf
[agent]
interval = "10s"
flush_interval = "10s"
# MQTT input plugin — subscribes to all factory topics
[[inputs.mqtt_consumer]]
servers = ["tcp://127.0.0.1:1883"]
topics = ["factory/#"]
username = "factoryuser"
password = "yourpassword"
data_format = "json"
json_time_key = "timestamp"
json_time_format = "2006-01-02T15:04:05.000Z"
tag_keys = ["line", "machine"]
# InfluxDB v2 output plugin
[[outputs.influxdb_v2]]
urls = ["http://127.0.0.1:8086"]
token = "YOUR_INFLUXDB_TOKEN"
organization = "factory"
bucket = "machines"
sudo systemctl enable telegraf
sudo systemctl start telegraf
sudo systemctl status telegraf
Verify data is being written to InfluxDB by querying the bucket from the InfluxDB UI
at http://localhost:8086 or from the CLI:
influx query 'from(bucket:"machines") |> range(start: -5m) |> limit(n:5)'
Step 5: Grafana Dashboard
sudo apt install -y apt-transport-https software-properties-common wget
sudo mkdir -p /etc/apt/keyrings/
wget -q -O - https://apt.grafana.com/gpg.key | gpg --dearmor | \
sudo tee /etc/apt/keyrings/grafana.gpg > /dev/null
echo "deb [signed-by=/etc/apt/keyrings/grafana.gpg] \
https://apt.grafana.com stable main" | \
sudo tee /etc/apt/sources.list.d/grafana.list
sudo apt update
sudo apt install -y grafana
sudo systemctl enable grafana-server
sudo systemctl start grafana-server
Open http://localhost:3000 and log in with admin and the password you set.
Add the InfluxDB data source
Go to Connections → Data sources → Add new data source and select InfluxDB.
Set Query Language to Flux, URL to http://localhost:8086,
organisation to factory, default bucket to machines,
and paste your InfluxDB API token. Click Save and test.
Create a machine monitoring dashboard
Go to Dashboards → New Dashboard → Add visualisation. Use a Stat panel for the latest temperature reading, a Time series panel for the trend over the last hour, and a Gauge panel for pressure with threshold colours configured to show green, yellow, and red zones.
Example Flux query for the latest temperature from line 1:
from(bucket: "machines")
|> range(start: -1h)
|> filter(fn: (r) => r["_measurement"] == "mqtt_consumer")
|> filter(fn: (r) => r["line"] == "line1")
|> filter(fn: (r) => r["_field"] == "temperature")
|> aggregateWindow(every: 30s, fn: mean, createEmpty: false)
|> yield(name: "mean")
In Grafana, go to Alerting → Alert rules → New alert rule. Define a threshold for example, temperature above 85°C triggers a critical alert. Configure a contact point to send notifications to Slack, email, PagerDuty, or a webhook. Grafana evaluates the rule against InfluxDB every minute and fires the alert when the condition is met.
Step 6: Remote Access to the Dashboard with Localtonet
The Grafana dashboard at port 3000 is only accessible on the gateway machine and local network by default. To give production managers, maintenance engineers, and off-site personnel access from any browser, create a Localtonet HTTP tunnel for port 3000.
curl -fsSL https://localtonet.com/install.sh | sh
localtonet --authtoken <YOUR_TOKEN>
Go to the HTTP tunnel page,
set local IP to 127.0.0.1 and port to 3000.
Click Create and start the tunnel.
The dashboard shows a public HTTPS URL share it with authorised personnel.
Register Localtonet as a service so the tunnel comes back automatically after a gateway reboot:
sudo localtonet --install-service --authtoken <YOUR_TOKEN>
sudo localtonet --start-service --authtoken <YOUR_TOKEN>
Create separate Grafana user accounts for remote viewers and assign them the Viewer role. Viewers can see dashboards and query data but cannot modify panels, data sources, or alert rules. The Admin account should only be used from within the plant network. Configure this under Administration → Users in Grafana.
Security Recommendations
🔐 Enable SSO on the Localtonet tunnel
In the Localtonet dashboard, enable Single Sign-On on the HTTP tunnel. Anyone opening the Grafana URL must authenticate via Google, GitHub, Microsoft, or GitLab before the Grafana login page loads. This adds an identity verification layer in front of Grafana's own authentication. See the SSO documentation for setup steps.
🔒 Change Grafana's default admin password immediately
The default admin/admin credentials are the first combination any automated scanner will try.
Change the admin password on first login and do not reuse it for any other service.
📊 This stack is read-only for the machines
The architecture described here only reads Modbus TCP registers. It does not write to any PLC register or issue any control commands. The Modbus connection is purely for data acquisition. If you later need write capability for setpoint adjustments, that path should be implemented as a separate, explicitly audited flow with its own access controls.
💾 Back up the InfluxDB data regularly
Production data has value beyond the monitoring window. Schedule regular InfluxDB backups
to an external location. The influx backup command creates a portable snapshot
of the entire database that can be restored on any InfluxDB instance.
# Create a dated backup
influx backup /var/backups/influxdb/$(date +%Y%m%d) \
--token YOUR_INFLUXDB_TOKEN
Frequently Asked Questions
Can this stack work with PLCs that only support Modbus RTU over serial?
Yes, with a Modbus RTU to TCP gateway. A Moxa NPort serial server, a ProSoft gateway, or a USB-to-RS485 adapter on the gateway machine can expose a serial Modbus RTU device as a Modbus TCP server. The Node-RED modbus-read node then connects to the gateway's IP address exactly as it would to a native Modbus TCP device. The rest of the stack is unchanged.
How many machines can one gateway handle?
A Raspberry Pi 4 with 4 GB RAM running this stack comfortably handles 20 to 30 Modbus TCP devices polled every 2 to 5 seconds, depending on the number of registers read per device and the complexity of the Node-RED flows. For larger installations 50 to 100 devices or more an industrial x86 edge PC is recommended. Node-RED supports multiple modbus-read nodes running concurrently, each pointing at a different PLC IP address.
What happens to historical data if the gateway machine reboots?
InfluxDB persists data to disk and survives reboots. Data collected before the reboot is fully retained. During the reboot window, no new data is collected and no Modbus polling occurs. Node-RED, Mosquitto, Telegraf, InfluxDB, and Grafana all have their systemd services configured to start automatically, so the gap in data is limited to the boot time of the machine, typically under a minute.
Can I add alarm notifications when a value goes out of range?
Yes, at two levels. In Node-RED, add a switch node after the function node to check values in real time and trigger an MQTT message to a dedicated alarm topic when a threshold is crossed. In Grafana, configure alert rules with threshold conditions and route them to contact points; email, Slack, PagerDuty, Teams, or a custom webhook. The Grafana approach covers historical and aggregated alerts while the Node-RED approach handles immediate real-time detection without needing Grafana to be running.
How long can I store data before InfluxDB runs out of space?
InfluxDB with the default retention policy of 30 days stores the data for that window and then automatically deletes older records. A single Modbus data point saved every 2 seconds from 20 devices generates roughly 500 to 800 MB per month depending on field count. A 32 GB SD card on a Raspberry Pi provides comfortable headroom for 30-day retention. For longer retention, use a USB SSD or configure InfluxDB's downsampling tasks to aggregate older data to lower resolution before the deletion window.
Your Factory Floor, Visible from Anywhere
Deploy the MING stack on a gateway machine inside your plant, configure a Node-RED flow to poll your Modbus devices, and open a Localtonet tunnel to Grafana. Production managers get a live dashboard in their browser without any VPN or firewall changes.
Create Free Localtonet Account →