Connecting ESP32 and Node.js to a Cloud MQTT Server with TLS

Protonest IoT
9 min readNov 29, 2024

--

If you need realtime data transmission between an IoT device and a web/mobile application, using an MQTT server is one of the best options.

This guide provides a detailed approach to securely connect your ESP32 and Node.js application to a cloud-based MQTT server using TLS encryption, including the necessary steps for adding both the intermediate and root CA certificates.

After that, you will be able to make the APIs to connect with your mobile/ web application.

We’ll cover,

  • Running the MQTT Broker
  • Obtaining the intermediate and root CA certificates.
  • Configuring the certificates in your ESP32 code.
  • Configuring the certificates in your Node.js application.
  • Ensuring a secure and trusted TLS connection.

Prerequisites

Hardware

Software

  • Arduino IDE (with ESP32 board installed)
  • Node.js and npm installed on your computer
  • Postman(API testing tool)

Accounts

  • Account with a cloud MQTT broker provider that supports TLS (we are using HiveMQ Cloud in this article)

Certificates

  • Access to both the intermediate and root CA certificates of your MQTT broker.

Understanding TLS Certificates

Root CA Certificate

  • Issued by a trusted Certificate Authority (CA).
  • Used to sign other certificates.
  • Trusted by clients and systems.

Intermediate CA Certificate

  • Issued by the Root CA.
  • Used to create a certificate chain.
  • Enhances security by adding layers.

Certificate Chain

  • The server presents a certificate chain to the client during the TLS handshake.
  • The client must have the certificates to validate the chain.
  • Including both the intermediate and root CA certificates ensures the client can verify the server’s certificate.

Setting Up a Cloud MQTT Broker with TLS

  1. Choose a Cloud MQTT Broker. We’ll use HiveMQ Cloud in this article.
  2. Create an Account and Set Up the Broker. Visit HiveMQ Cloud and create a free account.
  3. Create a New Cluster with the serverless free plan.
  • After logging in, create a new MQTT cluster.
  • Note down the broker’s hostname and port (8883 for TLS).

4. Configure Credentials

  • Create a new MQTT username and password for your clients.
  • Note these credentials for later use.

5. Obtain the Certificates

  • You’ll need both the Intermediate and Root CA certificates.

Type the below command on the terminal,

openssl s_client -showcerts -connect your-broker-hostname:8883

Add your broker hostname on the command, then you will get both certificates as a response to the command.

6. Create a Combined Certificate File

Place the Intermediate Certificate first, followed by the Root Certificate according to the below example.

-----BEGIN CERTIFICATE-----
(Intermediate certificate content)
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
(Root certificate content)
-----END CERTIFICATE-----

Connecting ESP32 to MQTT Broker over TLS

Install Required Libraries

  • Install PubSubClient and WiFiClientSecure libraries.

ESP32 Code Implementation

A. Include Necessary Libraries

#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>

B. Define Wi-Fi and MQTT Credentials

// Wi-Fi credentials
const char* ssid = "your_wifi_ssid";
const char* password = "your_wifi_password";
// MQTT Broker settings
const char* mqtt_server = "your_broker_hostname";
const int mqtt_port = 8883;
const char* mqtt_user = "your_mqtt_username";
const char* mqtt_pass = "your_mqtt_password";

C. Include Both Certificates

Load the combined certificates as below,

const char* ca_cert = \
"-----BEGIN CERTIFICATE-----\n" \
"(Intermediate certificate content)\n" \
"-----END CERTIFICATE-----\n" \
"-----BEGIN CERTIFICATE-----\n" \
"(Root certificate content)\n" \
"-----END CERTIFICATE-----\n";

Note: Ensure that each certificate starts and ends with the appropriate lines. Include the newline characters \n.

D. Initialize Wi-Fi and MQTT Clients

WiFiClientSecure espClient;
PubSubClient client(espClient);

E. Synchronize Time

  • Required for TLS to verify certificates.
void syncTime() {
configTime(0, 0, "pool.ntp.org", "time.nist.gov");
Serial.print("Waiting for NTP time sync: ");
time_t now = time(nullptr);
while (now < 8 * 3600 * 2) {
delay(500);
Serial.print(".");
now = time(nullptr);
}
Serial.println("\nTime synchronized");
}

F. Setup Function

void setup() {
Serial.begin(115200);

// Connect to Wi-Fi
WiFi.begin(ssid, password);
// Wait for Wi-Fi connection...

// Synchronize time
syncTime();

// Configure MQTT with TLS
espClient.setCACert(ca_cert);

client.setServer(mqtt_server, mqtt_port);
client.setCallback(callback);
}

G. MQTT Callback Function

void callback(char* topic, byte* payload, unsigned int length) {
// Handle incoming messages
Serial.print("Message arrived on topic: ");
Serial.println(topic);
}

H. Reconnect Function

void reconnect() {
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
String clientId = "ESP32Client-" + String(random(0xffff), HEX);
if (client.connect(clientId.c_str(), mqtt_user, mqtt_pass)) {
Serial.println("connected");
client.subscribe(topicCommands.c_str());
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" retrying in 5 seconds");
delay(5000);
}
}
}

I. Main Loop

void loop() {
if (!client.connected()) {
reconnect();
}
client.loop();

// Publish data
publishData();

delay(5000);
}

J. Complete ESP32 Code Example

#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>

// Wi-Fi credentials
const char* ssid = "your_wifi_ssid";
const char* password = "your_wifi_password";

// MQTT Broker settings
const char* mqtt_server = "your_broker_hostname";
const int mqtt_port = 8883;
const char* mqtt_user = "your_mqtt_username";
const char* mqtt_pass = "your_mqtt_password";

// MQTT Topics
String strollerId = "stroller_001";
String topicData = "stroller/" + strollerId + "/data";
String topicCommands = "backend/" + strollerId + "/commands";

// Combined Intermediate and Root CA Certificates
const char* ca_cert = \
"-----BEGIN CERTIFICATE-----\n" \
"(Intermediate certificate content)\n" \
"-----END CERTIFICATE-----\n" \
"-----BEGIN CERTIFICATE-----\n" \
"(Root certificate content)\n" \
"-----END CERTIFICATE-----\n";

WiFiClientSecure espClient;
PubSubClient client(espClient);

// Function prototypes
void setup_wifi();
void syncTime();
void reconnect();
void callback(char* topic, byte* message, unsigned int length);
void publishData();

void setup() {
Serial.begin(115200);

// Connect to Wi-Fi
setup_wifi();

// Synchronize time
syncTime();

// Configure MQTT with TLS
espClient.setCACert(ca_cert);

client.setServer(mqtt_server, mqtt_port);
client.setCallback(callback);
}

void loop() {
if (!client.connected()) {
reconnect();
}
client.loop();

// Publish data
publishData();

delay(5000);
}

void setup_wifi() {
Serial.print("Connecting to Wi-Fi...");
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nConnected to Wi-Fi");
}

void syncTime() {
configTime(0, 0, "pool.ntp.org", "time.nist.gov");
Serial.print("Waiting for NTP time sync: ");
time_t now = time(nullptr);
while (now < 8 * 3600 * 2) {
delay(500);
Serial.print(".");
now = time(nullptr);
}
Serial.println("\nTime synchronized");
}

void reconnect() {
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
String clientId = "ESP32Client-" + String(random(0xffff), HEX);
if (client.connect(clientId.c_str(), mqtt_user, mqtt_pass)) {
Serial.println("connected");
client.subscribe(topicCommands.c_str());
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" retrying in 5 seconds");
delay(5000);
}
}
}

void callback(char* topic, byte* payload, unsigned int length) {
// Handle incoming messages
Serial.print("Message arrived on topic: ");
Serial.println(topic);
}

void publishData() {
StaticJsonDocument<256> doc;
doc["temperature"] = 25.0; // Replace with actual sensor reading
doc["humidity"] = 50.0; // Replace with actual sensor reading
doc["status"] = "All good"; // Replace with actual status

char jsonBuffer[512];
size_t n = serializeJson(doc, jsonBuffer);
client.publish(topicData.c_str(), jsonBuffer, n);
Serial.println("Published data");
}

Here is the output you get,

Connecting Node.js Application to MQTT Broker over TLS

Initialize a Node.js Project

  • Run the following commands on the terminal to create a new folder and initialze npm
mkdir mqtt_backend
cd mqtt_backend
npm init -y

Install Required Packages

Install the libraries through the following command,

npm install mqtt express body-parser cors ws

Nodejs Code Implementation

A. Require Necessary Modules

const mqtt = require('mqtt');
const express = require('express');
const bodyParser = require('body-parser');
const fs = require('fs');
const path = require('path');

B. Initialize Express App

// Express Server for APIs
const app = express();
const port = 3000;

// Middleware
app.use(bodyParser.json());

C. Connect to MQTT Broker with TLS

const mqttOptions = {
host: 'your_broker_hostname',
port: 8883,
protocol: 'mqtts',
username: 'your_mqtt_username',
password: 'your_mqtt_password',
rejectUnauthorized: true,
};

// Initialize MQTT Client
console.log('Initializing MQTT client...');
const mqttClient = mqtt.connect(mqttOptions);

D. MQTT Client Event Handling

// MQTT Client Event Handlers
mqttClient.on('connect', () => {
console.log('Connected to MQTT broker');

// Subscribe to data topic
mqttClient.subscribe(topicData, (err) => {
if (!err) {
console.log(`Subscribed to ${topicData}`);
} else {
console.error(`Subscription error:`, err);
}
});
});

mqttClient.on('error', (error) => {
console.error('MQTT Connection Error:', error);
});

mqttClient.on('message', (topic, message) => {
console.log(`Received message on topic ${topic}: ${message.toString()}`);

if (topic === topicData) {
try {
const parsedData = JSON.parse(message.toString());
handleIncomingData(parsedData);
} catch (err) {
console.error('Failed to parse incoming data:', err);
}
}
});

E. Server Setup

// Start Express Server
app.listen(port, () => {
console.log(`API server is running on port ${port}`);
});

F. Complete Nodejs Code

Copy and paste the code and save the code as “mqtt_server.js”

// mqtt_backend_update.js
const mqtt = require('mqtt');
const express = require('express');
const bodyParser = require('body-parser');
const fs = require('fs');
const path = require('path');

// MQTT Broker Configuration
const mqttOptions = {
host: 'your_broker_hostname', // Replace with your MQTT broker hostname
port: 8883, // TLS port
protocol: 'mqtts',
username: 'your_mqtt_username', // Replace with your MQTT username
password: 'your_mqtt_password', // Replace with your MQTT password
rejectUnauthorized: true,
};

// MQTT Topics
const strollerId = 'stroller_001';
const topicData = `stroller/${strollerId}/data`;
const topicCommands = `backend/${strollerId}/commands`;

// Initialize MQTT Client
console.log('Initializing MQTT client...');
const mqttClient = mqtt.connect(mqttOptions);

// Data Store
let latestData = {
temperature: null,
humidity: null,
status: null,
};

// MQTT Client Event Handlers
mqttClient.on('connect', () => {
console.log('Connected to MQTT broker');

// Subscribe to data topic
mqttClient.subscribe(topicData, (err) => {
if (!err) {
console.log(`Subscribed to ${topicData}`);
} else {
console.error(`Subscription error:`, err);
}
});
});

mqttClient.on('error', (error) => {
console.error('MQTT Connection Error:', error);
});

mqttClient.on('message', (topic, message) => {
console.log(`Received message on topic ${topic}: ${message.toString()}`);

if (topic === topicData) {
try {
const parsedData = JSON.parse(message.toString());
handleIncomingData(parsedData);
} catch (err) {
console.error('Failed to parse incoming data:', err);
}
}
});

// Function to Handle Incoming Data
function handleIncomingData(data) {
console.log('Processing incoming data:', data);

if (data.temperature !== undefined) {
latestData.temperature = data.temperature;
}

if (data.humidity !== undefined) {
latestData.humidity = data.humidity;
}

if (data.status !== undefined) {
latestData.status = data.status;
}

console.log('Updated latest data:', latestData);
}

// Express Server for APIs
const app = express();
const port = 3000;

// Middleware
app.use(bodyParser.json());

// API: Get Latest Data
app.get('/api/latest-data', (req, res) => {
console.log('Received request for latest data');
res.status(200).send({ success: true, data: latestData });
});

// API: Send Command to ESP32
app.post('/api/command', (req, res) => {
const { command } = req.body;
console.log(`Received command: ${command}`);

if (typeof command !== 'string' || command.trim() === '') {
return res.status(400).send({ success: false, message: 'Invalid command' });
}

// Publish the command to the MQTT topic
mqttClient.publish(topicCommands, command.trim(), (err) => {
if (err) {
console.error('Failed to publish command:', err);
return res.status(500).send({ success: false, message: 'Failed to send command' });
}
console.log('Command published successfully');
res.status(200).send({ success: true, message: 'Command sent' });
});
});

// Start Express Server
app.listen(port, () => {
console.log(`API server is running on port ${port}`);
});

Run the code

On the terminal, you can run the following command to execute the code,

node mqtt_server.js

You will get the logs as,

Implementing RESTful APIs in Node.js

  • Implement your APIs to interact with the MQTT broker, using the updated connection settings with the certificates.
  • Ensure that any MQTT publishing or subscribing is done through the mqttClient configured with the certificates.

Testing the System

  • Open the postman application,
  1. Send a GET request to the endpoint,

http://<ip>:3000/api/latest-data

IP can be obtained from your computer's Wi-Fi settings. The computer you are using to run the API, nodejs script and the esp32 should be connected to the same WiFi.

You will get a response,

{
"success": true,
"data": {
"temperature": 25.0,
"humidity": 50.0,
"status": "All good"
}
}

2. Send a Post request to,

http://<ip>:3000/api/command

on the Json file, you can add,

{
"command": "START"
}

You will get a response,

{
"success": true,
"message": "Command sent"
}

The esp32 receives the sent command.

You can use WebSockets to reduce the latency even more.

Ready to start your IoT journey? Visit Protonest’s IoT System Design Tool and bring your project to life today!

https://iot-system-design-tool.protonest.co/

If you have questions or need assistance, contact support at info@protonest.co.

Contact us for any consultations or projects related to IoT and embedded systems.

Email: info@protonest.co

Protonest for more details.

Protonest specializes in transforming IoT ideas into reality. We offer prototyping services from concept to completion. Our commitment ensures that your visionary IoT concepts become tangible, innovative, and advanced prototypes.

Our Website: https://www.protonest.co/

Cheers!

--

--

Protonest IoT
Protonest IoT

Written by Protonest IoT

We make your IoT ideas a reality

No responses yet