Deployment guide

Node-RED Mosquitto InfluxDB Grafana

Overview

Most IoT projects need the same four things: a broker for devices, a processing engine, a time series database and dashboards. This SecureAnyCloud image delivers the IoT Stack (Node-RED, Mosquitto, InfluxDB, Grafana) on Ubuntu 24.04 on Azure, already assembled and wired together:

Tier Software Role

  • Ingress: Eclipse Mosquitto 2.1, the MQTT broker your devices connect to and publish readings into.
  • Processing: Node-RED 5.0, a flow-based, low-code engine that parses, reshapes and routes each reading.
  • Storage: InfluxDB 2.9, a time series database that keeps every reading on a timeline.
  • Presentation: Grafana 13.2 OSS, dashboards over the stored series.

The path between the four is already built. Node-RED ships with a flow that subscribes to the broker, converts incoming JSON readings into InfluxDB line protocol and writes them to the database. Grafana ships with the InfluxDB data source and a starter telemetry dashboard already provisioned against the same bucket.

Publish a reading from any MQTT client and it travels from broker to flow to database to chart, without you configuring a single connection.

From there, the flow editor is where you extend the pipeline. Add device-specific parsing, alerting, filtering or extra destinations, using the same drag-and-drop editor the shipped flow is built in.

The security model, in one paragraph

Each of these four projects ships with an insecure default, and this image closes all four before the virtual machine is ever reachable. Mosquitto ships with an empty password store, so with anonymous access disabled every connection is refused. InfluxDB ships un-onboarded, so its API rejects every call and there is no credential to steal.

Node-RED and Grafana are held shut behind a boot gate and cannot start at all until one first-boot routine has generated a fresh password, token and secret for each of the four services. The routine writes them to a file that only root can read. There is no known or default credential anywhere in the image, and no two virtual machines share one.

Only four ports are reachable from outside the machine. The database, the flow runtime and the dashboard server are all bound to the loopback interface behind a single TLS terminator, whose certificate is also generated on your own machine at first boot.

Prerequisites

  • An Azure subscription with permission to create virtual machines, and the Azure CLI installed and signed in if you deploy from the command line.
  • An SSH key pair. Azure uses your public key for the azureuser account.
  • A client for testing. The examples use mosquitto_pub and mosquitto_sub (Debian and Ubuntu: apt-get install mosquitto-clients; macOS: brew install mosquitto), plus a browser.
  • Somewhere to keep secrets. First boot generates five credentials and writes them to a root-only file. Copy them into your password manager.
1

Deploy from the Azure Portal

  • In the Azure Portal, choose Create a resource and search the Marketplace for IoT Stack (Node-RED, Mosquitto, InfluxDB, Grafana), published by SecureAnyCloud.
  • Select the offer, then Create.
  • On the Basics tab, pick your subscription, resource group and region. Set the Size to Standard_B2ms or larger. This is a four-service stack. 2 vCPUs and 8 GiB of memory is the recommended floor, and a 4 GiB size runs but leaves no headroom once real ingest starts.
  • Set Authentication type to SSH public key, leave the username as azureuser, and paste your public key.
  • Under Inbound port rules, allow SSH (22) for now. Step 3 opens ports 443 and 8883.
  • Accept the defaults on the remaining tabs, then choose Review + create and Create.
2

Deploy from the Azure CLI

Substitute your own resource group, region and virtual machine name in the commands below.

az group create --name iot-stack-rg --location uksouth
az vm create \
  --resource-group iot-stack-rg \
  --name iot-stack-vm \
  --image sac1647283583153:nodered-mosquitto-influxdb-grafana:default:latest \
  --size Standard_B2ms \
  --admin-username azureuser \
  --generate-ssh-keys \
  --public-ip-sku Standard
# Accept the marketplace terms once per subscription, if prompted:
az vm image terms accept --publisher sac \
  --offer nodered-mosquitto-influxdb-grafana --plan default
3

Open the ports your devices and dashboards need

The image itself ships no firewall rules. Open only the ports you need, and scope the source ranges to networks you control rather than leaving them open to the internet.

az network nsg rule create --resource-group iot-stack-rg --nsg-name iot-stack-vmNSG \
  --name allow-https --priority 1010 --destination-port-ranges 443 \
  --source-address-prefixes <your-mgmt-cidr> --protocol Tcp --access Allow
az network nsg rule create --resource-group iot-stack-rg --nsg-name iot-stack-vmNSG \
  --name allow-mqtt-tls --priority 1020 --destination-port-ranges 8883 \
  --source-address-prefixes <your-device-cidr> --protocol Tcp --access Allow

Port 80 serves only an unauthenticated /health endpoint and a redirect to HTTPS. You do not need to expose it unless something in your environment runs health checks over plain HTTP.

4

Retrieve the credentials this virtual machine generated

First boot generates a separate secret for every tier and writes them to a root-only file. Nothing is shared between virtual machines, and nothing was baked into the image. Read the file:

sudo cat /root/nodered-mosquitto-influxdb-grafana-credentials.txt

If the file does not yet list the generated secrets, first boot has not finished. Watch its progress:

sudo journalctl -u iot-stack-firstboot.service --no-pager | tail -30

First boot does not simply create secrets and exit. It proves the whole pipeline before it declares success. It:

  • Round-trips a message through the broker on both the loopback and the TLS listener.
  • Confirms that anonymous MQTT is refused.
  • Onboards InfluxDB.
  • Checks that the Node-RED runtime answers.
  • Confirms that Grafana rejects the upstream default password.
  • Publishes a reading and reads that exact point back out of InfluxDB.

If the integration is not working, first boot fails loudly rather than leaving you a half-wired appliance.

5

Check the stack came up

systemctl is-active mosquitto influxdb node-red grafana-server nginx

All five should report active. You can also confirm the network posture, which is a deliberate part of this image. Check which ports are reachable:

echo "reachable from off-box:" ss -tlnH | awk '{print $4}' | sed -E 's/%[A-Za-z0-9._-]+//' | grep -vE '^(127\.|\[::1\])' | sort -u
echo "loopback only:" ss -tlnH | awk '{print $4}' | grep '^127\.0\.0\.1' | sort -u

Exactly four ports answer from outside the machine:

  • 22 (SSH)
  • 80 (health check and redirect)
  • 443 (both web interfaces)
  • 8883 (MQTT over TLS)

The flow runtime, the dashboard server, the database and the plain-text MQTT listener are all bound to 127.0.0.1. They cannot be reached from the network at all.

6

Sign in to the Node-RED flow editor

Browse to https://<vm-ip>/ and sign in as admin with NODE_RED_ADMIN_PASSWORD from Step 4.

The certificate is self-signed and generated on your own virtual machine, so your browser warns you on the first visit. Step 13 covers replacing it with a certificate issued by a certificate authority.

Once signed in, you land on the IoT Pipeline flow. This is the wiring that makes the four packages one product.

Reading the flow left to right and top to bottom:

  • Publish demo reading is an inject node. Click the button on its left edge to publish a synthetic reading through the real broker. This is the quickest way to see the pipeline move.
  • subscribe sensors/# receives everything published under the sensors/ topic tree, whatever the device.
  • to InfluxDB line protocol parses the JSON payload, turns each numeric field into an InfluxDB field and tags the point with the device name and topic.
  • write to InfluxDB posts that line protocol to the local database. Its write token comes from the systemd environment file, so no secret is stored inside the flow.
  • append to mqtt-inbox.log and received reading give you a file trail and a live view in the debug sidebar.
7

Publish your first device reading over MQTT with TLS

Devices connect to port 8883 with the broker username and password from Step 4. From the virtual machine itself, you can test the whole path immediately:

MQ=$(sudo grep '^MOSQUITTO_PASSWORD=' /root/nodered-mosquitto-influxdb-grafana-credentials.txt | cut -d= -f2-) mosquitto_pub -h 127.0.0.1 -p 8883 --cafile /etc/sac/tls/server.crt --insecure \
  -u sac -P "$MQ" -t sensors/press-line-07 \
  -m '{"device":"press-line-07","temperature":74.2,"humidity":21.8}' echo "published"

From a device or workstation elsewhere, make the same call with your own address and the certificate copied off the machine:

mosquitto_pub -h <vm-ip> -p 8883 --cafile ./server.crt \
  -u sac -P '<MOSQUITTO_PASSWORD>' -t sensors/press-line-07 \
  -m '{"device":"press-line-07","temperature":74.2,"humidity":21.8}'

The broker refuses anything unauthenticated. Both of these commands are rejected:

if mosquitto_pub -h 127.0.0.1 -t sensors/probe -m x >/dev/null 2>&1; then echo "UNEXPECTED: an anonymous publish was accepted"; exit 1 fi echo "anonymous publish correctly refused" if mosquitto_pub -h 127.0.0.1 -u sac -P wrong-password -t sensors/probe -m x >/dev/null 2>&1; then echo "UNEXPECTED: a wrong password was accepted"; exit 1 fi echo "wrong password correctly refused"

Confirm that the flow received the reading:

sleep 3
sudo tail -3 /var/lib/node-red/mqtt-inbox.log

Any JSON object works. Every numeric field becomes an InfluxDB field, so {"device":"tank-3","level":88.5,"flow_rate":12.1} is stored with two fields tagged device=tank-3.

A payload with no numeric fields is logged and skipped rather than written.

8

Confirm the reading landed in InfluxDB

The flow writes into the iot bucket of the SecureAnyCloud organisation. Read the reading straight back:

IX=$(sudo grep '^INFLUXDB_ADMIN_TOKEN=' /root/nodered-mosquitto-influxdb-grafana-credentials.txt | cut -d= -f2-)
curl -fsS -XPOST 'http://127.0.0.1:8086/api/v2/query?org=sac' \
  -H "Authorization: Token ${IX}" \
  -H 'Content-Type: application/vnd.flux' \
  -H 'Accept: application/csv' \
  --data-binary 'from(bucket:"iot") |> range(start:-15m) |> filter(fn:(r) => r._measurement == "sensor") |> keep(columns:["_time","_field","_value","device"])'

The database is bound to the loopback interface, so that request works only from the machine itself. An unauthenticated request is refused even there:

curl -s -o /dev/null -w '%{http_code}\n' 'http://127.0.0.1:8086/api/v2/buckets?org=sac'
9

Open the Grafana dashboard

Browse to https://<vm-ip>/grafana/ and sign in as admin with GRAFANA_ADMIN_PASSWORD from Step 4. Anonymous access is disabled and the upstream admin / admin default is never valid on this image.

Open Dashboards, then the SecureAnyCloud folder, then IoT Telemetry (SecureAnyCloud). The InfluxDB data source is already provisioned and pointed at the same bucket the flow writes to, so readings appear without any configuration.

The dashboard is editable and is provisioned from a file. Your changes persist in Grafana, while the shipped version stays on disk at /var/lib/grafana/dashboards/iot-telemetry.json as a reference to copy from.

10

Connect a real device

Anything that speaks MQTT works: an ESP32 or ESP8266 with the PubSubClient library, a Raspberry Pi with Paho, an industrial gateway, or another Node-RED instance. Point it at:

  • Host: your virtual machine's address.
  • Port: 8883.
  • TLS: enabled, trusting /etc/sac/tls/server.crt. Copy it to the device, or replace the certificate as in Step 13 so devices trust it through a normal certificate authority.
  • Username: SecureAnyCloud.
  • Password: MOSQUITTO_PASSWORD from Step 4.
  • Topic: anything under sensors/, conventionally sensors/<device-name>.

Copy the certificate to your workstation with:

sudo cp /etc/sac/tls/server.crt /tmp/server.crt && sudo chmod 0644 /tmp/server.crt echo "now fetch /tmp/server.crt with scp"

To give each device its own broker credential rather than sharing one, add users to the password file and restart the broker:

sudo mosquitto_passwd -b /etc/mosquitto/passwd press-line-07 'a-strong-per-device-password'
sudo systemctl restart mosquitto
sudo mosquitto_passwd -D /etc/mosquitto/passwd press-line-07
sudo systemctl restart mosquitto

The example above adds a device user and then removes it again, so the machine is left as it was. Drop the last two lines when you are provisioning for real.

For per-device topic restrictions, add an access control list with acl_file in /etc/mosquitto/conf.d/sac.conf.

11

Extend the pipeline in Node-RED

The shipped flow is a starting point, not a fixed appliance. Common next steps, all done in the editor:

  • Parse a different payload. Replace the to InfluxDB line protocol function with your own device format, for example a CSV or binary payload from an existing fleet.
  • Alert. Add a switch node after subscribe sensors/# and wire it to an email, Slack or webhook node when a value crosses a threshold.
  • Route somewhere else as well. The MQTT input node can feed as many outputs as you like. Add a second HTTP request node to forward readings to another system.
  • Downsample. If devices publish faster than you want to store, add a delay node in rate-limit mode, or use an InfluxDB task.

Click Deploy in the editor after changing a flow.

The write token is available inside function nodes as env.get("INFLUX_TOKEN"), so you never need to paste a secret into a flow.

12

Reach the InfluxDB user interface

InfluxDB has a built-in query and administration interface, but it is deliberately not exposed to the network. Reach it over an SSH tunnel from your workstation:

ssh -L 8086:127.0.0.1:8086 azureuser@<vm-ip>

With the tunnel running, browse to http://127.0.0.1:8086/ and sign in as admin with INFLUXDB_ADMIN_PASSWORD.

Use this interface to manage retention, create scoped API tokens for other applications, and build Flux queries interactively.

13

Replace the TLS certificate

The certificate generated at first boot is self-signed and unique to this machine. For production, replace it with one from a certificate authority. Point a real DNS name at the machine, then run:

sudo apt-get install -y certbot
sudo certbot certonly --standalone -d <your-domain> --agree-tos -m <your-email> --non-interactive sudo cp /etc/letsencrypt/live/<your-domain>/fullchain.pem /etc/sac/tls/server.crt
sudo cp /etc/letsencrypt/live/<your-domain>/privkey.pem /etc/sac/tls/server.key
sudo install -o root -g mosquitto -m 0644 /etc/sac/tls/server.crt /etc/mosquitto/certs/server.crt
sudo install -o root -g mosquitto -m 0640 /etc/sac/tls/server.key /etc/mosquitto/certs/server.key
sudo systemctl reload nginx && sudo systemctl restart mosquitto

Both nginx and Mosquitto read from /etc/sac/tls/, so replacing that pair covers the dashboards, the flow editor and the device listener.

A helper regenerates a self-signed pair only when those files are absent, so your real certificate is not overwritten.

14

What is exposed, and what is not

Port Bound to Serves Authentication

22 all interfaces SSH your Azure SSH key

80 all interfaces /health and a redirect to HTTPS none, by design

443 all interfaces Node-RED at /, Grafana at /grafana/ per virtual machine passwords

8883 all interfaces MQTT over TLS broker username and password

1883 loopback plaintext MQTT for the local flow broker username and password

1880 loopback Node-RED runtime behind nginx editor authentication

3000 loopback Grafana behind nginx Grafana login

8086 loopback InfluxDB API and interface API token

The plain-text MQTT listener is loopback-only on purpose. It is how Node-RED talks to the broker on the same machine, and exposing it would put device credentials on the wire in clear text. If you must expose it, change the bind address in /etc/mosquitto/conf.d/sac.conf and open the port, but prefer 8883.

15

Retention, disk and backups

InfluxDB stores its data under /var/lib/influxdb on the operating system disk. The shipped bucket has no retention limit, so it grows until you set one.

Set a retention period from the InfluxDB interface (Step 12), or from the machine:

IX=$(sudo grep '^INFLUXDB_ADMIN_TOKEN=' /root/nodered-mosquitto-influxdb-grafana-credentials.txt | cut -d= -f2-) influx bucket list --host http://127.0.0.1:8086 --org sac --token "$IX"

To keep 90 days, for example, use influx bucket update with --retention 90d and the bucket ID from that listing.

For a high-ingest deployment, move the database onto its own managed disk. Attach and mount a data disk, stop influxdb, and copy /var/lib/influxdb across. Update bolt-path and engine-path in /etc/influxdb/config.toml, then start InfluxDB again.

Back up by snapshotting the disk, or with the InfluxDB backup command:

IX=$(sudo grep '^INFLUXDB_ADMIN_TOKEN=' /root/nodered-mosquitto-influxdb-grafana-credentials.txt | cut -d= -f2-) sudo mkdir -p /var/backups/influxdb
sudo influx backup /var/backups/influxdb/$(date +%F) --host http://127.0.0.1:8086 --org sac --token "$IX"

Send that backup to durable storage rather than leaving it on the machine's own disk.

Your Node-RED flows live in /var/lib/node-red/flows.json, and your Grafana dashboards live in Grafana's own database at /var/lib/grafana/grafana.db. Include both in any backup.

16

Verify the deployment

Run this single pass to exercise every tier end to end:

CF=/root/nodered-mosquitto-influxdb-grafana-credentials.txt
MQ=$(sudo grep '^MOSQUITTO_PASSWORD=' $CF | cut -d= -f2-)
IX=$(sudo grep '^INFLUXDB_ADMIN_TOKEN=' $CF | cut -d= -f2-) DEV=verify-$(date +%s)
systemctl is-active mosquitto influxdb node-red grafana-server nginx mosquitto_pub -h 127.0.0.1 -p 8883 --cafile /etc/sac/tls/server.crt --insecure \
  -u sac -P "$MQ" -t "sensors/${DEV}" \
  -m "{\"device\":\"${DEV}\",\"temperature\":20.5,\"humidity\":50.0}" sleep 5 curl -fsS -XPOST 'http://127.0.0.1:8086/api/v2/query?org=sac' \
  -H "Authorization: Token ${IX}" \
  -H 'Content-Type: application/vnd.flux' \
  -H 'Accept: application/csv' \
  --data-binary "from(bucket:\"iot\") |> range(start:-10m) |> filter(fn:(r) => r.device == \"${DEV}\")" \
  | grep -q "${DEV}" && echo "END-TO-END OK: MQTT -> Node-RED -> InfluxDB" curl -sk -o /dev/null -w 'grafana %{http_code}\n' https://127.0.0.1/grafana/login curl -s -o /dev/null -w 'health %{http_code}\n' http://127.0.0.1/health

The output shows five active lines, END-TO-END OK, and two 200 responses.

Baked versions and configuration

Component Version Source Licence

Eclipse Mosquitto 2.1.2 ppa:mosquitto-dev/mosquitto-ppa (the project's own Ubuntu channel) EPL-2.0 or EDL-1.0, redistributed under EDL-1.0

Node-RED 5.0.7 npm, global install Apache-2.0

Node.js 24 LTS NodeSource MIT

InfluxDB 2.9.1 repos.influxdata.com MIT

Grafana OSS 13.2.1 apt.grafana.com, the grafana package AGPL-3.0-only

nginx 1.24 (Ubuntu) Ubuntu 24.04 BSD-2-Clause

Key paths:

Path Contents

/root/nodered-mosquitto-influxdb-grafana-credentials.txt the five per virtual machine secrets

/etc/mosquitto/conf.d/sac.conf broker listeners, authentication and TLS

/var/lib/node-red/flows.json the shipped IoT Pipeline flow

/etc/sac/node-red.env the InfluxDB write token used by the flow

/etc/influxdb/config.toml database bind address and paths

/etc/grafana/provisioning/ the InfluxDB data source and dashboard provider

/var/lib/grafana/dashboards/iot-telemetry.json the shipped dashboard

/etc/sac/tls/ the TLS certificate and key

/usr/local/sbin/iot-stack-firstboot.sh the first boot bootstrap

Licences and trademarks

This image bundles four independent open-source projects, unmodified. Their licence texts ship in the image under /usr/share/sac/licenses/nodered-mosquitto-influxdb-grafana/.

  • Node-RED is Apache-2.0.
  • Eclipse Mosquitto is dual-licensed EPL-2.0 or EDL-1.0, and is redistributed here under the EDL-1.0 arm.
  • InfluxDB 2 is MIT.
  • Grafana is AGPL-3.0-only. This image installs the open-source grafana package and does not install Grafana Enterprise or any Enterprise plugin.

Node-RED is a registered trademark of the OpenJS Foundation. Eclipse Mosquitto is a trademark of the Eclipse Foundation AISBL. InfluxDB is a trademark of InfluxData, Inc. Grafana is a registered trademark of Grafana Labs.

These names are used nominatively, to identify the unmodified open-source software contained in this image. SecureAnyCloud is not affiliated with, and this image is not endorsed by, the OpenJS Foundation, the Eclipse Foundation, InfluxData, Inc. or Grafana Labs.

Security notes

  • No shared or default credential. Every secret is generated on your own virtual machine at first boot. The image ships an empty broker password store, an un-onboarded database, an unusable editor password hash and a Grafana password nobody holds.
  • The upstream defaults are actively refused. Anonymous MQTT is rejected, and Grafana's admin / admin does not work on this image.
  • Four ports, not eight. The database, flow runtime, dashboard server and plain-text broker listener are bound to loopback.
  • Per-virtual-machine TLS. The certificate and private key are generated on your machine, and no two deployments share a key. Replace them with a pair issued by a certificate authority for production (Step 13).
  • No telemetry. Node-RED's update and telemetry prompt is answered off in the shipped settings, and Grafana's usage reporting and update checks are disabled, so the appliance does not phone home.
  • Scope your network rules. Restrict 443 to the networks that need the dashboards and 8883 to the networks your devices publish from.
  • Rotate the broker password and give each device its own credential (Step 10) before putting a fleet on it.
  • The base image is fully patched at build time and keeps unattended security upgrades enabled.

Support

SecureAnyCloud publishes and maintains this image with 24/7 support. For help with deployment or configuration, contact support through the SecureAnyCloud listing on the Azure Marketplace.

Deploy on Azure

Launch IoT Stack (Node-RED, Mosquitto, InfluxDB, Grafana) on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.

View this image

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on Azure