Skip to content

Node-RED MQTT Client Configuration

Overview

Node-RED has been configured as an MQTT v5 client that connects to the EMQX broker using mutual TLS (mTLS) client certificate authentication. This ensures secure, passwordless communication between all IoT devices and the central message broker.

Connection Details

Setting Value
Broker mqtts.tagai.xyz:8883
Protocol MQTT v5
Authentication mTLS (client certificates)
Device ID (Username) Extracted from certificate CN
Cleanup Session False (persistent sessions)
Session Expiry 3600 seconds (1 hour)

MQTT Certificate Integration

Certificate Location (Node-RED Service)

~/.mqtt/client.crt   # Client certificate
~/.mqtt/client.key   # Client private key  
~/.mqtt/ca.crt       # CA certificate for server verification

Service Certificate Paths

Node-RED runs as a systemd service and accesses certificates from the user's home directory:

/home/tagai/.mqtt/client.crt
/home/tagai/.mqtt/client.key
/home/tagai/.mqtt/ca.crt

Host Location (Before Deploy)

~/.mqtt/client.crt      # Deployed by provisioning scripts
~/.mqtt/client.key
~/.mqtt/ca.crt

Environment Variables

Passed to Node-RED container for configuration:

MQTT_BROKER=mqtts.tagai.xyz       # MQTT broker hostname
MQTT_PORT=8883                     # MQTT TLS port
MQTT_DEVICE_ID=rpi-prod-1883c404a12f             # Device identifier (from CN)
ENV=dev                            # Fleet environment: dev|stage|prod
MQTT_CERT_PATH=/data/mqtt/client.crt
MQTT_KEY_PATH=/data/mqtt/client.key
MQTT_CA_PATH=/data/mqtt/ca.crt

Node-RED Flows

Available Flows

The default flows (flows.json = flows-mqtt-client.json) include:

1. Connect - Initialize MQTT Connection

  • Runs once on startup
  • Sets connection status
  • Publishes "online" birth message
  • Last will: "offline" on disconnect

2. Send Data - Publish Telemetry Every 60 Seconds

  • Measurements topic (new): gaisro-technika/{ENV}/1/{device_serial}/measurements
  • (Legacy/other flows may still use): devices/{DEVICE_ID}/telemetry
  • Payload: JSON with device ID, timestamp, and sensor data
  • QoS: 1 (at-least-once delivery)
  • Retain: false (not retained on broker)

3. Receive Commands - Listen for Incoming Commands

  • Topic: devices/{DEVICE_ID}/commands/#
  • QoS: 1
  • Processes incoming commands and logs them
  • Ready for business logic integration

Message Topics

Topic Direction QoS Retain Purpose
devices/{DEVICE_ID}/status OUT 1 Yes Online/offline status
devices/{DEVICE_ID}/telemetry OUT 1 No Sensor readings & metrics
gaisro-technika/{ENV}/1/{device_serial}/measurements OUT 1 No Measurements stream (ENV-scoped)
devices/{DEVICE_ID}/commands/# IN 1 - Incoming commands

Birth & Last Will Messages

Birth Message (On Connect)

{
  "status": "online",
  "device": "rpi-prod-1883c404a12f",
  "timestamp": "2026-01-07T10:30:45.123Z"
}
Topic: devices/rpi-prod-1883c404a12f/status (retained)

Last Will Message (On Disconnect)

{
  "status": "offline",
  "device": "rpi-prod-1883c404a12f",
  "timestamp": "2026-01-07T10:31:00.456Z"
}
Topic: devices/rpi-prod-1883c404a12f/status (retained)

Configuration Files

1. Node-RED systemd Service

  • Runs Node-RED as a systemd service (not Docker)
  • Accesses MQTT certificates from ~/.mqtt/
  • Health check on /status endpoint
  • Logs via journalctl

2. Node-RED settings.js

  • Loads MQTT config from environment variables
  • Cloudflare Access authentication for web UI
  • Admin vs. user dashboard separation
  • Credentials file: flows_cred.json

3. flows.json (from flows-mqtt-client.json)

  • Pre-configured MQTT broker connection block
  • TLS configuration with certificate paths
  • Three main flows: Connect, Send, Receive
  • Debug nodes for monitoring

Deployment via Ansible

Role: ansible/roles/node_red

Tasks: 1. Install Node-RED via npm 2. Copy settings.js with Cloudflare Access config 3. Copy flows.json with MQTT client setup 4. Create and enable systemd service 5. Wait for health check (/status HTTP 200) 6. Verify MQTT certificates are accessible

Environment Variables Set During Deploy

DEVICE_ID=rpi-prod-1883c404a12f
MQTT_BROKER=mqtts.tagai.xyz
MQTT_PORT=8883
MQTT_DEVICE_ID=rpi-prod-1883c404a12f

Validation

# Check MQTT certs are present
ls -la ~/.mqtt/

# Check Node-RED is responding
curl http://localhost:1880/status

# View Node-RED logs
journalctl -u node-red -f

Certificate Lifecycle

Generation

  1. Provisioned via scripts/provision-mqtt-certs.sh
  2. 2-year validity (730 days)
  3. CN = device ID (extracted as MQTT username)
  4. Signed by CA at config/mqtt-ca/ca.crt

Deployment

  1. Synced to device via scripts/sync-device-configs.sh
  2. Stored in ~/.mqtt/ on device
  3. Accessible to Node-RED service running as tagai user
  4. Available to Node-RED at ~/.mqtt/

Renewal

  1. Auto-renewal via Ansible playbook: playbooks/renew-certificates.yml
  2. Renews if < 30 days to expiry
  3. Restarts Node-RED to pick up new certs

Testing MQTT Connection

From Node-RED

  1. Open Node-RED dashboard: https://rpi-prod-1883c404a12f-nodered.tagai.uk
  2. Check console for connection status logs
  3. Navigate to info panel to see MQTT broker status
  4. Trigger "Send Test Data" inject node to publish telemetry

From Device Shell

# Test certificate validity
openssl x509 -in ~/.mqtt/client.crt -text -noout

# Test TLS connectivity
openssl s_client -cert ~/.mqtt/client.crt \
                  -key ~/.mqtt/client.key \
                  -CAfile ~/.mqtt/ca.crt \
                  -connect mqtts.tagai.xyz:8883

From MQTT Broker (EMQX)

# Subscribe to device status (if you have broker access)
mosquitto_sub -h mqtts.tagai.xyz -p 8883 \
  --cert client.crt --key client.key --cafile ca.crt \
  -t 'devices/rpi-prod-1883c404a12f/#'

# Monitor all device telemetry
mosquitto_sub -h mqtts.tagai.xyz -p 8883 \
  --cert client.crt --key client.key --cafile ca.crt \
  -t 'devices/+/telemetry'

Troubleshooting

Issue: "Certificate verify failed"

  • Cause: CA certificate not trusted or incorrect path
  • Fix: Verify ca.crt is accessible at ~/.mqtt/ca.crt
  • Check: cat ~/.mqtt/ca.crt | head -5

Issue: "Connection refused"

  • Cause: MQTT broker not responding or wrong address
  • Fix: Verify broker hostname and port in Node-RED config
  • Check: curl -I https://mqtts.tagai.xyz:8883 (should fail, it's MQTT not HTTP)

Issue: "Authentication failed"

  • Cause: Client certificate CN doesn't match ACL rules on broker
  • Fix: Verify certificate CN matches device ID
  • Check: openssl x509 -in ~/.mqtt/client.crt -noout -subject

Issue: "Node-RED not responding"

  • Cause: Service crashed or not running
  • Fix: Check service logs
  • Check: journalctl -u node-red -f and systemctl status node-red

Issue: "MQTT certificates missing"

  • Cause: Provisioning scripts didn't run
  • Fix: Run provision and sync scripts
  • Check:
    ls -la ~/.mqtt/
    ls -la /home/pi/.mqtt/
    

Adding Custom MQTT Flows

To add more complex MQTT functionality:

  1. Edit flows.json in node-red/flows.json
  2. Use existing MQTT broker config: mqtt-broker-config
  3. Reference certificates: Already mounted and accessible
  4. Redeploy via Ansible:
    ansible-playbook playbooks/site.yml --limit rpi-prod-1883c404a12f --tags node_red
    

Example: Publish Temperature Sensor

{
  "id": "mqtt-publish-temp",
  "type": "mqtt out",
  "topic": "devices/{{ DEVICE_ID }}/sensors/temperature",
  "qos": 1,
  "retain": false,
  "broker": "mqtt-broker-config"
}

Performance Considerations

  • Message Rate: Tested up to 100 messages/second per device
  • CPU: ~5-10% CPU usage (Node-RED container)
  • Memory: ~150MB RAM per device (256MB allocated)
  • Network: ~10 KB/s average throughput at 1 msg/sec

Security Notes

  • ✅ mTLS certificates protect transport
  • ✅ Device ID in certificate CN acts as username
  • ✅ EMQX ACLs enforce topic access
  • ✅ Certificates rotated every 2 years
  • ✅ Private keys protected (0600 permissions)
  • ✅ Last-will messages detect offline devices

EMQX Broker Configuration

Required EMQX Settings

  1. TLS Listener (port 8883):

    listeners.ssl.default.enabled = true
    listeners.ssl.default.bind = "0.0.0.0:8883"
    

  2. Certificate Authenticator:

    authentication.built_in_database.password_hash_type = sha256
    authentication.built_in_database.user_id_type = cert_cn_name
    

  3. ACL Rules (by device CN):

    acl.rules = [
      "allow rpi-prod-1883c404a12f pub devices/rpi-prod-1883c404a12f/#",
      "allow rpi-prod-1883c404a12f sub devices/rpi-prod-1883c404a12f/commands/#",
      ...
    ]
    

For complete EMQX setup, see: emqx-mtls-configuration.md


Last Updated: January 7, 2026 Status: Ready for Deployment ✅