ESP32 and Arduino

ESP8266 MQTT Tutorial: Connect a NodeMCU to a Cloud Broker (Plain and TLS)

Connect an ESP8266 NodeMCU to an MQTT broker with PubSubClient, publish JSON telemetry, control the onboard LED, then add TLS with BearSSL while keeping the limited heap under control.

NodeMCU ESP8266 board on a breadboard sending ESP8266 MQTT messages to a cloud broker
Run this on your own MQTT brokerGet a private, managed MQTT broker on the Free plan and start from ready-made code samples.
Try it free Code samples No card required

The ESP8266 is still one of the cheapest ways to put a sensor on Wi-Fi, and ESP8266 MQTT projects are a natural fit for its small footprint. In this tutorial you will connect a NodeMCU or Wemos D1 mini to a broker with PubSubClient, publish JSON telemetry, switch the onboard LED from your computer, and then upgrade to TLS without running out of memory.

What You Need

  • A NodeMCU v2/v3, Wemos D1 mini or any ESP8266 board with USB.
  • Arduino IDE 2.x with the "esp8266 by ESP8266 Community" board package (add https://arduino.esp8266.com/stable/package_esp8266com_index.json under File → Preferences → Additional boards manager URLs).
  • The PubSubClient library by Nick O'Leary from the Library Manager.
  • The Mosquitto command-line tools on your computer for testing.
  • A 2.4 GHz Wi-Fi network. The ESP8266 does not support 5 GHz.

For the first steps you will use broker.connectmq.com, the free public MQTT broker operated by ConnectMQ. It accepts anonymous clients on port 1883 with any non-empty, unique client ID. Treat it as a public notice board: everything you publish there can be read by others.

Board Settings That Matter

Before writing code, set these in the Tools menu:

Setting Recommended value Why
Board NodeMCU 1.0 (ESP-12E Module) or LOLIN(WEMOS) D1 mini Correct flash layout and pin names
CPU Frequency 160 MHz for TLS, 80 MHz otherwise TLS handshakes are much faster at 160 MHz
SSL Support All SSL ciphers (default) or Basic SSL ciphers Basic saves flash, still works with modern brokers
lwIP Variant v2 Lower Memory Leaves more heap for your sketch
Upload Speed 921600 (drop to 115200 if uploads fail) Faster iteration

Step 1: Your First ESP8266 MQTT Sketch

The sketch below uses the onboard LED on GPIO2 (LED_BUILTIN), which is active-low on most modules. It publishes uptime, signal strength and free heap every five seconds, and listens for ON or OFF commands.

#include <ESP8266WiFi.h>
#include <PubSubClient.h>

const char* WIFI_SSID     = "YOUR_WIFI";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";

const char* MQTT_HOST = "broker.connectmq.com";
const uint16_t MQTT_PORT = 1883;

char deviceId[24];
char topicTelemetry[64];
char topicLedSet[64];
char topicLedState[64];

WiFiClient netClient;
PubSubClient mqtt(netClient);

unsigned long lastPublish = 0;
unsigned long lastAttempt = 0;

void setLed(bool on) {
  digitalWrite(LED_BUILTIN, on ? LOW : HIGH);   // active-low
  mqtt.publish(topicLedState, on ? "ON" : "OFF", true);
}

void onMessage(char* topic, byte* payload, unsigned int length) {
  char msg[16];
  unsigned int n = length < sizeof(msg) - 1 ? length : sizeof(msg) - 1;
  memcpy(msg, payload, n);
  msg[n] = '\0';
  Serial.printf("[%s] %s\n", topic, msg);

  if (strcmp(msg, "ON") == 0)  setLed(true);
  if (strcmp(msg, "OFF") == 0) setLed(false);
}

void connectWifi() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  Serial.print("Wi-Fi");
  while (WiFi.status() != WL_CONNECTED) {
    delay(300);
    Serial.print(".");
  }
  Serial.printf(" connected, IP %s\n", WiFi.localIP().toString().c_str());
}

bool connectMqtt() {
  if (mqtt.connect(deviceId, nullptr, nullptr, topicLedState, 1, true, "offline")) {
    Serial.println("MQTT connected");
    mqtt.subscribe(topicLedSet, 1);
    setLed(false);
    return true;
  }
  Serial.printf("MQTT failed, rc=%d\n", mqtt.state());
  return false;
}

void setup() {
  Serial.begin(115200);
  pinMode(LED_BUILTIN, OUTPUT);

  // Unique ID from the chip ID, e.g. esp8266-3f2a1b
  snprintf(deviceId, sizeof(deviceId), "esp8266-%06x", ESP.getChipId());
  snprintf(topicTelemetry, sizeof(topicTelemetry), "connectmq/demo/%s/telemetry", deviceId);
  snprintf(topicLedSet,    sizeof(topicLedSet),    "connectmq/demo/%s/led/set", deviceId);
  snprintf(topicLedState,  sizeof(topicLedState),  "connectmq/demo/%s/led/state", deviceId);
  Serial.printf("\nDevice ID: %s\n", deviceId);

  connectWifi();
  mqtt.setServer(MQTT_HOST, MQTT_PORT);
  mqtt.setCallback(onMessage);
  mqtt.setKeepAlive(30);
}

void loop() {
  if (WiFi.status() != WL_CONNECTED) connectWifi();

  if (!mqtt.connected()) {
    if (millis() - lastAttempt > 5000) {
      lastAttempt = millis();
      connectMqtt();
    }
    return;
  }
  mqtt.loop();

  if (millis() - lastPublish > 5000) {
    lastPublish = millis();
    char payload[96];
    snprintf(payload, sizeof(payload), "{\"uptime\":%lu,\"rssi\":%d,\"heap\":%u}",
             millis() / 1000, WiFi.RSSI(), ESP.getFreeHeap());
    mqtt.publish(topicTelemetry, payload);
  }
}

Open the serial monitor at 115200 baud and note the device ID, then subscribe from your computer (replace the ID with yours):

mosquitto_sub -h broker.connectmq.com -p 1883 -i laptop-3f2a1b -t "connectmq/demo/esp8266-3f2a1b/#" -v

Switch the LED:

mosquitto_pub -h broker.connectmq.com -p 1883 -i ctl-3f2a1b -t "connectmq/demo/esp8266-3f2a1b/led/set" -m ON

The led/state topic is retained, so any new subscriber immediately learns whether the LED is on, and the Last Will sets it to offline if the board loses power.

Step 2: Understand the ESP8266 Memory Budget

Once the plain sketch works, check the heap value it publishes. A simple sketch usually leaves a comfortable amount of free heap, but TLS changes the picture. A BearSSL session with default buffers needs a large contiguous block of memory for its receive and transmit buffers plus certificate validation work, and the ESP8266 has only a fraction of the RAM of an ESP32.

Practical consequences:

  • Keep the PubSubClient buffer as small as your messages allow. Calling setBufferSize(4096) "just in case" may be the difference between a working and a failing handshake.
  • Avoid heavy use of the String class in long-running sketches; repeated allocations fragment the heap and the TLS handshake may fail days later even though free heap looks fine. ESP.getMaxFreeBlockSize() shows the largest contiguous block, which is the number that matters.
  • Build JSON with snprintf or a fixed-size ArduinoJson document rather than string concatenation.

If you regularly need large payloads, read PubSubClient buffer size for streaming techniques that avoid big buffers.

Step 3: ESP8266 MQTT over TLS

For production, your device should connect to a private broker over TLS on port 8883 with its own credentials. With a managed MQTT broker such as ConnectMQ, you get a username, password and client ID from the portal. The server certificate is issued by Let's Encrypt, so you trust the ISRG Root X1 root certificate.

The ESP8266 core uses BearSSL. Unlike the ESP32, it checks certificate validity dates, so the clock must be correct before the handshake.

#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include <time.h>

// ISRG Root X1 (Let's Encrypt root), paste the full PEM
static const char ROOT_CA[] PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
...ISRG Root X1...
-----END CERTIFICATE-----
)EOF";

BearSSL::X509List trustAnchors(ROOT_CA);
BearSSL::WiFiClientSecure tlsClient;
PubSubClient mqtt(tlsClient);

void syncClock() {
  configTime(0, 0, "pool.ntp.org", "time.nist.gov");
  Serial.print("Waiting for NTP");
  time_t now = time(nullptr);
  while (now < 1700000000) {      // any timestamp well in the past
    delay(250);
    Serial.print(".");
    now = time(nullptr);
  }
  Serial.println(" done");
}

void setupTls() {
  syncClock();
  tlsClient.setTrustAnchors(&trustAnchors);
  // Optional: smaller buffers if the server supports max fragment length
  if (tlsClient.probeMaxFragmentLength("broker.connectmq.com", 8883, 1024)) {
    tlsClient.setBufferSizes(1024, 1024);
  }
  mqtt.setServer("broker.connectmq.com", 8883);
}

bool connectMqtt() {
  return mqtt.connect("YOUR_CLIENT_ID", "YOUR_USERNAME", "YOUR_PASSWORD");
}

Call setupTls() in setup() after Wi-Fi connects. Set CPU frequency to 160 MHz; the first handshake can take a noticeable moment at 80 MHz, and your watchdog will not like a slow connect() inside a busy loop.

Step 4: Battery-Powered ESP8266 MQTT with Deep Sleep

Many ESP8266 sensors run on batteries and only need to report every few minutes. The radio is by far the biggest power consumer, so the winning pattern is: wake up, connect, publish, disconnect, sleep. Deep sleep on the ESP8266 requires a hardware link between GPIO16 (D0 on NodeMCU and D1 mini) and the RST pin, because the chip wakes up by resetting itself. Disconnect that wire while uploading if your board misbehaves during flashing.

void publishAndSleep() {
  connectWifi();
  mqtt.setServer(MQTT_HOST, MQTT_PORT);
  if (mqtt.connect(deviceId)) {
    char payload[64];
    snprintf(payload, sizeof(payload), "{\"a0\":%d,\"rssi\":%d}", analogRead(A0), WiFi.RSSI());
    mqtt.publish(topicTelemetry, payload);
    // give the stack a moment to flush the packet before we cut power to the radio
    for (int i = 0; i < 10; i++) { mqtt.loop(); delay(10); }
    mqtt.disconnect();
  }
  ESP.deepSleep(5 * 60 * 1000000ULL);   // 5 minutes, in microseconds
}

Call publishAndSleep() at the end of setup() and leave loop() empty. A few practical notes: a TLS handshake on every wake-up costs noticeably more time and energy than plain MQTT, so weigh the security requirement against battery life and consider longer sleep intervals with TLS. Use a retained message for the latest reading, so dashboards that open between wake-ups still see a value. And do not use a Last Will for presence on sleepy devices; a clean disconnect() never triggers it, and an "offline" status would be misleading anyway.

Troubleshooting ESP8266 MQTT Problems

  1. rc=-2 on TLS but fine on 1883: clock not synced, wrong root certificate, or not enough contiguous heap. Print tlsClient.getLastSSLError() for the BearSSL reason. Our PubSubClient rc=-2 guide covers every code.
  2. Random resets (wdt reset): something blocks too long. Never put long delay() calls in the MQTT callback, and call yield() in long loops.
  3. Board never joins Wi-Fi: 5 GHz-only network, wrong password, or a captive portal on a guest network.
  4. Board disconnects whenever a second board connects: both boards use the same client ID. The chip-ID approach above prevents this.
  5. Messages over a few hundred bytes vanish: the default 256-byte PubSubClient buffer. Increase it moderately.
  6. Messages stop arriving after hours: heap fragmentation. Watch getMaxFreeBlockSize() over time.

ESP8266 or ESP32?

The ESP8266 is perfectly capable for single-sensor nodes, relays and small displays, especially on plain MQTT or occasional TLS connections. If you need many concurrent TLS sessions, Bluetooth, more GPIO or room for OTA updates plus large payloads, move to the ESP32. Our ESP32 vs ESP8266 comparison goes through the trade-offs, and the ESP32 IoT tutorial is the next step when you outgrow this board.

Run Your ESP8266 Fleet on ConnectMQ

When the prototype works, move it to a cloud MQTT broker with private credentials and TLS. ConnectMQ supports MQTT 3.1.1 and 5.0 on TCP 1883 and TLS 8883, and the portal includes an in-browser MQTT client and live topic explorer for checking what your boards publish. Create a free Serverless broker at app.connectmq.com, no credit card required.

Frequently Asked Questions

Can the ESP8266 use MQTT over TLS?

Yes. The ESP8266 core ships BearSSL, and PubSubClient works with BearSSL::WiFiClientSecure. Sync the clock first, trust the broker's root certificate and keep other memory use modest.

Which pins does the NodeMCU onboard LED use?

On most NodeMCU and Wemos D1 mini boards it is GPIO2 (LED_BUILTIN, also labelled D4), and it is active-low, so writing LOW turns it on.

Can I use the ESP8266 with MQTT 5?

PubSubClient only supports MQTT 3.1.1, which is enough for most sensors. The broker can still serve MQTT 5 clients, such as dashboards and backends, on the same topics.

Why does my ESP8266 need the time for TLS?

BearSSL checks that the server certificate is within its validity period. Without a correct clock the device thinks it is 1970 and rejects the certificate.

Related articles

ESP-IDF MQTT Example: Build an esp-mqtt Client from Scratch

Build an ESP-IDF project from an empty folder with the esp-mqtt component: Wi-Fi station setup, the MQTT event handler, publish and subscribe, MQTT 5 properties and TLS with the certificate bundle.

8 min read

Using esp-mqtt MQTT 5 Features on ESP32 with ESP-IDF

A hands-on ESP-IDF project that enables MQTT 5.0 in esp-mqtt and uses user properties, message expiry, topic aliases, session expiry and response topics against a real MQTT 5 broker.

7 min read

All articles →

Your private MQTT broker is a few clicks away

Create an account, get your broker credentials and connect your first device — no servers to install, patch or scale.