Tutorials

Arduino MQTT over Ethernet Shield (W5100/W5500) with PubSubClient

Connect an Arduino Uno or Mega with a W5100/W5500 Ethernet Shield to an MQTT broker using PubSubClient: wiring, DHCP, reconnects, RAM limits and testing.

Updated · 8 min read

Arduino MQTT over Ethernet Shield (W5100/W5500) with PubSubClient

An Arduino Uno or Mega with an Ethernet Shield is still one of the most reliable ways to put a sensor or relay on MQTT: no Wi-Fi to drop, no radio interference, and a wired link that just works in a cabinet or a basement. The standard stack is the Ethernet library for the WIZnet chip plus PubSubClient for MQTT.

This tutorial gets a W5100 or W5500 shield connected to a broker, publishing a sensor value and accepting commands, with a reconnect loop that survives cable pulls and broker restarts. Then you verify it end to end from the browser.

Hardware and libraries

PartNotes
Arduino Uno R3 or Mega 2560Uno: 2 KB RAM, Mega: 8 KB RAM
Ethernet Shield W5100 or Ethernet Shield 2 (W5500)Talks SPI, chip select on pin 10
Network cable to a router with DHCPOr a static IP on your LAN

Install two libraries from the Library Manager: Ethernet (by Arduino, supports W5100, W5200 and W5500) and PubSubClient (by Nick O'Leary). Most shields also carry a microSD slot whose chip select is pin 4; if no card is used, drive pin 4 high so it does not interfere with the SPI bus.

The complete sketch

ethernet_mqtt.ino
#include <SPI.h>
#include <Ethernet.h>
#include <PubSubClient.h>

// Use a unique MAC per board on your network
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0x10, 0x01 };

const char* MQTT_HOST = "broker.connectmq.com";
const uint16_t MQTT_PORT = 1883;
const char* CLIENT_ID = "arduino-eth-0001";
const char* MQTT_USER = "username";
const char* MQTT_PASS = "password";

const char* T_STATUS = "testmqtt/arduino-eth/0001/status";
const char* T_TEMP   = "testmqtt/arduino-eth/0001/temp";
const char* T_CMD    = "testmqtt/arduino-eth/0001/cmd/led";

EthernetClient ethClient;
PubSubClient mqtt(ethClient);

unsigned long lastReconnect = 0;
unsigned long lastPublish = 0;

void onMessage(char* topic, byte* payload, unsigned int length) {
  Serial.print(F("<- "));
  Serial.print(topic);
  Serial.print(F(" = "));
  Serial.write(payload, length);
  Serial.println();

  if (strcmp(topic, T_CMD) == 0 && length > 0) {
    digitalWrite(LED_BUILTIN, payload[0] == '1' ? HIGH : LOW);
  }
}

bool connectMqtt() {
  Serial.print(F("MQTT connecting... "));
  // id, user, pass, willTopic, willQos, willRetain, willMessage
  if (mqtt.connect(CLIENT_ID, MQTT_USER, MQTT_PASS, T_STATUS, 1, true, "offline")) {
    Serial.println(F("connected"));
    mqtt.publish(T_STATUS, "online", true);
    mqtt.subscribe(T_CMD, 1);
    return true;
  }
  Serial.print(F("failed, rc="));
  Serial.println(mqtt.state());
  return false;
}

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(4, OUTPUT);
  digitalWrite(4, HIGH);           // deselect the SD card
  Serial.begin(9600);

  Ethernet.init(10);               // W5100/W5500 chip select
  Serial.println(F("DHCP..."));
  if (Ethernet.begin(mac) == 0) {
    if (Ethernet.hardwareStatus() == EthernetNoHardware) {
      Serial.println(F("No Ethernet shield found"));
    } else {
      Serial.println(F("DHCP failed - check the cable or use a static IP"));
    }
    while (true) delay(1000);
  }
  Serial.print(F("IP: "));
  Serial.println(Ethernet.localIP());

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

void loop() {
  if (!mqtt.connected()) {
    unsigned long now = millis();
    if (now - lastReconnect > 5000) {   // retry every 5 s without blocking
      lastReconnect = now;
      connectMqtt();
    }
  } else {
    mqtt.loop();
  }

  if (mqtt.connected() && millis() - lastPublish > 10000) {
    lastPublish = millis();
    char buf[8];
    int raw = analogRead(A0);
    itoa(raw, buf, 10);
    mqtt.publish(T_TEMP, buf);
  }

  Ethernet.maintain();              // renew the DHCP lease
}

A few choices in this sketch matter more than they look:

  • F("...") strings keep constant text in flash instead of RAM. On an Uno, a dozen debug strings can otherwise eat a quarter of your memory.
  • The reconnect is non-blocking. loop() keeps running between attempts, so local logic (buttons, relays, a watchdog) does not freeze while the broker is unreachable.
  • mqtt.loop() must run often. It reads incoming packets and sends the keep-alive PINGREQ. A long delay() in your code makes the broker drop the connection after 1.5 × keep alive.
  • Last Will and a retained online give any dashboard a reliable online/offline flag. See MQTT Last Will.

Static IP instead of DHCP

Industrial networks often have no DHCP. Pass the address, DNS server, gateway and subnet explicitly; without a DNS entry the hostname broker.connectmq.com cannot be resolved:

Static configuration
IPAddress ip(192, 168, 1, 50);
IPAddress dns(192, 168, 1, 1);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);

Ethernet.begin(mac, ip, dns, gateway, subnet);

If DNS is unavailable, mqtt.setServer() also accepts an IPAddress, but then a change of broker IP breaks every device. Prefer a hostname whenever the network allows it.

Test it end to end

  1. Open the Serial Monitor at 9600 baud. You should see an IP address, then MQTT connecting... connected.
  2. Open the online MQTT client, connect to broker.connectmq.com over secure WebSocket (port 8084, path /mqtt) and subscribe to testmqtt/arduino-eth/0001/#.
  3. The retained status = online arrives immediately, followed by a temp value every ten seconds.
  4. Publish 1 to testmqtt/arduino-eth/0001/cmd/led. The on-board LED turns on and the Serial Monitor prints the message. Publish 0 to turn it off.
  5. Unplug the network cable. After about 45 seconds the broker sends the will and the status changes to offline. Plug it back in: the sketch reconnects within a few seconds and publishes online again.

Watch the Arduino in the online client

If you would rather test from a terminal, mosquitto_pub and mosquitto_sub do the same job: mosquitto_sub -h broker.connectmq.com -t 'testmqtt/arduino-eth/#' -v.

Reading mqtt.state()

When connect() fails, state() tells you why. These are the codes PubSubClient defines:

CodeConstantMeaning
-4MQTT_CONNECTION_TIMEOUTNo response within keep alive
-3MQTT_CONNECTION_LOSTNetwork connection broke
-2MQTT_CONNECT_FAILEDTCP connection failed (DNS, IP, port, firewall)
-1MQTT_DISCONNECTEDCleanly disconnected
1MQTT_CONNECT_BAD_PROTOCOLBroker rejected the protocol version
2MQTT_CONNECT_BAD_CLIENT_IDClient ID rejected
3MQTT_CONNECT_UNAVAILABLEBroker unavailable
4MQTT_CONNECT_BAD_CREDENTIALSWrong username or password
5MQTT_CONNECT_UNAUTHORIZEDNot authorized

failed, rc=-2 is by far the most common: the shield never reached the broker over TCP. Check that Ethernet.localIP() is not 0.0.0.0, that the DNS server is reachable, and that outbound port 1883 is allowed. The PubSubClient troubleshooting guide walks through every code in detail.

Memory and packet size limits

PubSubClient allocates one buffer for both incoming and outgoing packets. Its default size is 256 bytes, and that includes the fixed header, the topic and the payload. A packet larger than the buffer is silently dropped on receive and publish() returns false on send.

Raising the buffer
mqtt.setBufferSize(512);   // call once in setup(), before connect()

On an Uno, keep the buffer modest and keep topics short. You can work out exactly how large a packet will be with the packet size calculator. On a Mega, 1–2 KB buffers are realistic. If a large payload still fails, the packet too large guide covers broker-side limits too.

Other limits to plan around:

  • QoS 0 publishes only. PubSubClient publishes at QoS 0 and subscribes at QoS 0 or 1. Use a retained state topic or an application acknowledgement when delivery matters.
  • MQTT 3.1.1 only. No reason codes or MQTT 5 properties.
  • Socket count. The W5100 has 4 sockets and the W5500 has 8. DHCP and DNS use one briefly, so a sketch that also runs a web server can run out.

When to move beyond the Uno

The Uno + Ethernet combination is perfect for simple, wired, local devices. If you need TLS, larger JSON payloads or MQTT 5 features, the same PubSubClient code ports almost unchanged to an ESP32 with a W5500 or to Wi-Fi boards: see ESP32 with Arduino and MQTT. If you want QoS 1 publishing on AVR, the arduino-mqtt library is worth a look.

A wired Arduino on MQTT is mostly about discipline: unique MACs and client IDs, F() strings, a non-blocking reconnect and a loop that never stalls. Get the serial log to say connected, prove publish, command and Last Will from the online client, and the board will run untouched for years.

Frequently asked questions

Can an Arduino Uno use MQTT over TLS?

Not in practice. The ATmega328P has 2 KB of RAM, far too little for a TLS handshake, and the W5100/W5500 chips do not do TLS themselves. Use plain MQTT on a trusted network, a local gateway that forwards over TLS, or move to an ESP32 or a board with more memory.

Why does PubSubClient not receive my longer messages?

PubSubClient drops any packet larger than its buffer, which defaults to 256 bytes including the topic and headers. Call setBufferSize() with a larger value, keeping in mind that on an Uno every byte comes out of the 2 KB of RAM.

Does PubSubClient support QoS 1 publishing?

No. PubSubClient publishes at QoS 0 only and supports MQTT 3.1.1. It can subscribe with QoS 0 or 1. If you need QoS 1 publishes, add an application-level acknowledgement or use a different library.