Non-Blocking MQTT Auto-Reconnect on ESP32
An ESP32 that connects to MQTT once during startup is easy to demonstrate and easy to break. The router may reboot, the broker may restart, Wi-Fi coverage may fluctuate or the server may be temporarily unavailable. If firmware assumes the connection will never drop, sensor reporting and remote control can stop until someone manually resets the board.
This guide shows a reconnect pattern for Arduino-style ESP32 firmware using PubSubClient. It schedules retries with millis() instead of long blocking delays, increases the wait after repeated failures, resets the retry interval after success and re-subscribes to command topics each time a new MQTT session is established.
No reconnect strategy makes an unstable network reliable by itself. It should make failure recoverable while the device continues local responsibilities and reports availability honestly.
Design principles before code
- Keep retry timing separate from application work. Use unsigned millisecond arithmetic so wraparound is handled safely for short intervals.
- Do not spin in a tight loop. A disconnected broker should not consume all CPU time.
- Back off after repeated failure. Repeated network failures should not cause continuous reconnect traffic.
- Re-subscribe after reconnect. Treat subscriptions as a session-setup step unless your chosen client/session design guarantees otherwise.
- Use a unique client ID. Duplicate IDs can force another connection off the broker.
- Continue local work. Read sensors, enforce local limits and maintain safe hardware behavior when cloud messaging is down.
The example assumes PubSubClient and a broker reachable via plain MQTT for a controlled lab. Configure authentication and TLS as appropriate for the deployment; this is not a production security configuration by itself.
Hardware and library assumptions
Use an ESP32 development board, a Wi-Fi network, a reachable MQTT broker and the PubSubClient Arduino library. Add the library through Arduino IDE's Library Manager and confirm the API documented for your installed library. Configure a private SSID, Wi-Fi password, broker host, broker port and unique client ID locally.
Do not use 127.0.0.1 as the broker address in the ESP32 sketch. Use the LAN hostname/IP the board can route to. The broker must also be configured to accept that remote connection.
1. Reconnect scheduling pattern
The example uses a single loop to manage retry timing. The MQTT library's client.loop() must be called regularly while connected so it can process traffic and keep the session alive.
This is a reconnect skeleton. Fill configuration placeholders locally, add application-specific telemetry/command behavior, and test on your installed ESP32 core and PubSubClient version.
Important configuration note: this is a reconnect skeleton, not a complete secure product sketch. For a real deployment, configure broker authentication and TLS as supported by your client, protect credentials, choose safe behavior while offline, and test Wi-Fi recovery on the actual board/core version.
2. Why the retry interval grows
After an unsuccessful attempt, the interval doubles until it reaches a configured ceiling. This capped backoff reduces repeated connection attempts when the broker is offline while still retrying periodically. If the broker returns, the next successful connection resets the interval so future recovery is prompt.
A fixed retry interval can also be acceptable for a small lab, but avoid a zero-delay loop. For fleets of devices, add random jitter so many clients do not retry at exactly the same instant after a shared outage. Choose limits based on how quickly the system needs to recover and how much reconnect traffic the broker can handle.
3. Restore subscriptions and application state
When mqtt.connect succeeds, re-subscribe to each command or configuration topic the device needs. Check the return value if your library provides one, and publish status only after the session is usable. If using Last Will, configure it as part of the connect call so the broker can publish offline status after an unexpected loss.
Be careful with retained commands. A reconnect may deliver old retained data immediately after subscribing. Use retained state or desired configuration only when replaying it is intentional; do not accidentally retain a one-shot toggle or unlock command. Validate commands that affect physical outputs and, when appropriate, acknowledge with a separate reported-state publication.
4. Availability, Last Will and retained status
A common pattern is a retained online status after a successful connection and an LWT offline status registered at connect time. A dashboard can use that signal to distinguish a sensor reading still valid from one produced by a device that has vanished.
Availability status is not a substitute for last-seen timestamps. A broker may wait for the client's keep-alive timeout before publishing its Last Will. If the dashboard needs data freshness, track the time of the last valid reading separately and define a project-appropriate stale threshold.
5. Test failure cases, not just startup
- Boot with the broker offline, then start Mosquitto and confirm eventual connection.
- Connect successfully, stop the broker, and confirm local firmware continues to run.
- Restart the broker and confirm subscriptions are restored.
- Restart Wi-Fi or move the board out of coverage and return it.
- Connect another client with the same ID in a lab to understand why uniqueness matters.
- Inspect broker logs and the PubSubClient state value when a connection fails.
- Verify outputs do not unexpectedly change when retained topics are delivered.
Record results and exact library/core versions during development. Do not claim recovery-time or uptime guarantees without measuring them on the intended network and hardware. The current Arduino-ESP32 Wi-Fi API documentation describes connection and auto-reconnect behavior.
Frequently asked questions
Can I use a long delay between retries? It also delays sensing and other work placed in the loop. A scheduled retry is easier to integrate into a responsive device.
Is rc=-2 always a broker error? In PubSubClient it indicates a failed connection attempt; inspect network reachability, listener configuration and logs to find the root cause.
Should I reconnect on every loop iteration? No. Use a retry timer and backoff.
Do I need to subscribe again after reconnecting? With common client patterns, yes—treat subscriptions as part of session setup unless your persistent-session implementation explicitly provides otherwise.
Does reconnect logic make a device safe? No. Safety behavior while disconnected, power-up state and local interlocks must be designed separately.
Next steps
First configure the broker correctly with Install Mosquitto on Raspberry Pi 5, then use MQTT Troubleshooting: Connection Refused, rc=-2 and rc=5 when diagnosing a failed session.
A robust MQTT client is not one that never disconnects. It is one that recovers predictably, avoids flooding the broker, restores subscriptions, reports status honestly and keeps essential local behavior running while connectivity is unavailable.