esp32 intermediate 30 min

ESP32: hosted MQTT brokers vs self-hosting

Compare EMQX and HiveMQ free tiers against Mosquitto on a Raspberry Pi, wire an ESP32 to each, and pick the right broker for your home.

Code available for: ESP32 ArduinoArduino C
Published Sep 22, 2026

Last month I hit HiveMQ’s connection limit on a Tuesday afternoon because I left a test loop running with a 100 ms publish interval. The broker did exactly what free tiers do: it stopped accepting connections and my dashboard went blank until the counter reset. Nothing broke. But the lesson stuck: when your home automation depends on somebody else’s free tier, you are renting your infrastructure from a company that will eventually change the pricing page.

The trap here is that the comparison looks obvious in the wrong direction. Hosted brokers win the first hour (no server, no config, TLS included). Self-hosting wins every hour after that, because the broker stops being a dependency on someone else’s decisions. This post is the honest version of that comparison, with the same ESP32 sketch pointed at both.

What you need

Needed

  • ESP32 dev board (e.g. ESP32-DevKitC, about $8).
  • A Raspberry Pi (Pi 3B+ or newer, any model with Ethernet or Wi-Fi). This is the self-hosting half. If you already run one for Pi-hole or Home Assistant, you are done; it has room for a broker too.
  • MicroSD card for the Pi (16 GB is plenty).
  • 2 jumper wires, female-female.

Nice to have

  • Soldering iron + solder, if your ESP32 board ships with a bare header to attach.
  • Soldering iron stand, for a safe place to park the hot iron.
  • Helping hands, to hold the header straight while soldering.
  • Anti-static wristband, when handling bare modules on a dry winter day.
  • Wire stripper, for clean wire ends.

Wiring

PinConnect to
ESP32 3V3(no wiring needed for MQTT; this is a network tutorial)
USB cableESP32 to your computer for flashing and Serial output

The only hardware in this tutorial is the ESP32 itself. MQTT is a network protocol, so the “wiring” is TCP port 1883 (or 8883 with TLS). If you want a physical project to publish from, grab the wiring from the DHT22 tutorial and publish that temperature.

Install

On the Raspberry Pi (Mosquitto)

sudo apt update
sudo apt install -y mosquitto mosquitto-clients

Then edit the config:

sudo nano /etc/mosquitto/conf.d/local.conf

and add:

listener 1883
allow_anonymous false
password_file /etc/mosquitto/passwd

Create the password file and restart:

sudo mosquitto_passwd -c /etc/mosquitto/passwd brian
sudo systemctl restart mosquitto

Test it from another terminal:

mosquitto_sub -h <pi-ip-address> -u brian -P yourpassword -t "test/#"
mosquitto_pub -h <pi-ip-address> -u brian -P yourpassword -t "test/hello" -m "hi"

If the sub terminal prints hi, your broker works. That took maybe ten minutes, and none of it was a pricing page.

In the Arduino IDE

Arduino IDE >> Tools >> Board >> esp32 >> install if you have not already. Then Arduino IDE >> Sketch >> Include Library >> Manage Libraries >> search “PubSubClient” >> install Nick O’Leary’s PubSubClient.

The comparison table you actually want

EMQX Cloud serverlessHiveMQ Cloud freeMosquitto on a Pi
CostFree tier, then per-messageFree tier, 100 connectionsFree, runs on hardware you own
Connections on free tierSmall free tier, then paid100 devicesBounded by the Pi (hundreds easily)
Data retentionConfigurable, then paidMessages not stored on free tierAs long as your SD card lives
TLSIncludedIncluded, with SNIYes, with a cert and more config
Uptime guaranteeNone on free tierNone on free tierNone, but you control restarts
Data leaves your houseYesYesNo
Dies whenYou hit the free tier capYou hit the cap or they change termsYour SD card dies (back it up)
Real failure modeSilent throttling, quota alertsConnection refusals at the capPower blips, fixable with a watchdog

I am not going to pretend the hosted tiers are bad. They are good at what they do (e.g. a HiveMQ free tier behind a demo you show a client, or an EMQX serverless cluster for a project whose hardware lives in three countries). The dishonest version of this comparison pretends free-tier limits do not exist. The other dishonest version pretends a Pi in a closet is enterprise-grade. Both are true at the same time, which is why the recommendation below has a condition attached.

The code

The same sketch works against all three brokers. Only the constants change. This publishes a heartbeat every 10 seconds:

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

// ---- Pick ONE broker config ----

// A) Mosquitto on your Pi (self-hosted, default recommendation)
const char* WIFI_SSID   = "your-wifi";
const char* WIFI_PASS   = "your-password";
const char* MQTT_HOST   = "192.168.1.50";  // the Pi's IP
const int   MQTT_PORT   = 1883;
const char* MQTT_USER   = "brian";
const char* MQTT_PASS   = "yourpassword";

// B) HiveMQ Cloud free tier
// const char* MQTT_HOST = "abcd1234efgh.s1.eu.hivemq.cloud";
// const int   MQTT_PORT = 8883;
// const char* MQTT_USER = "your-hivemq-user";
// const char* MQTT_PASS = "your-hivemq-pass";
// (keep TLS on; HiveMQ Cloud requires it on 8883)

// C) EMQX serverless
// const char* MQTT_HOST = "xxxx.ala.cn-hangzhou.emqxsl.cn";
// const int   MQTT_PORT = 8883;
// (same TLS notes as HiveMQ)

WiFiClientSecure tlsClient;   // for 8883
// WiFiClient plainClient;    // for 1883 against your own broker
PubSubClient mqtt(tlsClient);

unsigned long lastPublish = 0;

void connectWiFi() {
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println();
  Serial.print("WiFi OK, IP: ");
  Serial.println(WiFi.localIP());
}

void connectMQTT() {
  while (!mqtt.connected()) {
    String clientId = "esp32-" + String((uint32_t)ESP.getEfuseMac(), HEX);
    bool ok = mqtt.connect(clientId.c_str(), MQTT_USER, MQTT_PASS);
    if (ok) {
      Serial.println("MQTT connected");
    } else {
      Serial.print("failed, rc=");
      Serial.print(mqtt.state());
      Serial.println(" retrying in 2s");
      delay(2000);
    }
  }
}

void setup() {
  Serial.begin(115200);
  delay(1000);
  // For a quick test we skip CA verification. For anything real, pin
  // the broker's CA cert (see the TLS in depth tutorial).
  tlsClient.setInsecure();
  mqtt.setServer(MQTT_HOST, MQTT_PORT);
  mqtt.setBufferSize(512);
  connectWiFi();
  connectMQTT();
}

void loop() {
  if (!mqtt.connected()) connectMQTT();
  mqtt.loop();

  if (millis() - lastPublish > 10000) {
    lastPublish = millis();
    char payload[64];
    snprintf(payload, sizeof(payload),
             "{\"heap\":%u,\"rssi\":%d}", ESP.getFreeHeap(), WiFi.RSSI());
    mqtt.publish("home/esp32/heartbeat", payload);
    Serial.print("published: ");
    Serial.println(payload);
  }
}

For option A, swap WiFiClientSecure tlsClient for WiFiClient plainClient and drop the setInsecure() line. Port 1883 on your own LAN is fine; the Pi never talks to the internet for this.

For options B and C you are on 8883 with TLS, and setInsecure() skips certificate verification. That is fine for a weekend test and wrong for anything you actually care about (the TLS in depth tutorial shows the cert-pinning version).

Which one should you actually run

Default to Mosquitto on the Pi. Here is the reasoning, honestly stated:

  1. Your data stays in your house. Whole-house energy data, door events, presence: that is a profile of your life. I do not hand that to a free tier.
  2. No quotas. A 100 ms publish loop will not get you throttled.
  3. No terms-of-service risk. Cloud free tiers change. A Pi does not read its own pricing page.
  4. It composes with everything else you self-host (Node-RED, InfluxDB, Home Assistant all eat MQTT from the same broker).

When hosted is the right call: the broker must be reachable from multiple locations you do not control, you cannot punch a hole through the network (campus Wi-Fi, corporate NAT), or you genuinely want zero servers. In those cases HiveMQ Cloud’s free tier is the one I would pick (100 connections, TLS enforced, decent dashboard). EMQX serverless is the better fit when you might grow into their paid tier, because the migration is a config change instead of a replatform.

One more honest note: self-hosting means you are the ops team. If the Pi reboots, your broker is down until it comes back. Add sudo systemctl enable mosquitto so it starts on boot, put the Pi on a small UPS if the power blips matter, and put a watchdog on the ESP32 side so it reconnects (the reconnect logic above already does).

What you learned

  • MQTT brokers are swappable infrastructure: the same sketch runs against Mosquitto, HiveMQ Cloud, or EMQX serverless with only constants changed.
  • Hosted free tiers trade control for convenience, and the trade goes badly exactly when your project starts working.
  • The self-hosted default is Mosquitto on a Pi: free, local, no quotas, and it slots into the rest of your home server stack.
  • Port 1883 is plain MQTT; 8883 is MQTT over TLS. Cloud brokers force TLS; your own broker can too when it faces the internet.

When something breaks

  • rc=-2 on connect (option A). The broker is not reachable. Ping the Pi. Then check sudo systemctl status mosquitto and make sure the listener config actually saved. The most common cause is editing /etc/mosquitto/mosquitto.conf instead of a file in conf.d/.
  • rc=-2 on connect (options B/C). You forgot TLS. Cloud brokers on 8883 need WiFiClientSecure, not WiFiClient. If you see a certificate error with setInsecure() removed, that is the missing CA cert, not a wrong password.
  • Connection works, publishes vanish. The default PubSubClient buffer is 256 bytes and some cloud brokers require larger packets. Call mqtt.setBufferSize(512) before setServer().
  • Password auth fails on your own Mosquitto. You created the password file but forgot to restart the service, or allow_anonymous is still true with no password_file line. Restart, then test with mosquitto_pub from a laptop before blaming the ESP32.
  • Works on the bench, dies on your Wi-Fi. Some routers isolate wireless clients (AP isolation). The ESP32 cannot see the Pi. Disable client isolation for your IoT network or move both onto 2.4 GHz.

What to build next

  • The MQTT publish-subscribe tutorial is the protocol-level follow up: topics, QoS, retained messages, and last-will.
  • The ntfy notifications tutorial pushes a message to your phone from the same ESP32 when a threshold trips (e.g. publish to MQTT and fire an ntfy alert from the same loop).
  • The Raspberry Pi Mosquitto tutorial goes deeper on the broker side: bridging, websockets, and keeping the broker alive across reboots.
  • The ADS1115 external ADC tutorial pairs with this for real sensor data worth publishing.